Merge remote-tracking branch 'origin' into mfa

This commit is contained in:
Tuan Dang
2023-02-18 16:49:51 +07:00
31 changed files with 1295 additions and 330 deletions
+6 -6
View File
@@ -3,8 +3,8 @@ import { Switch } from '@headlessui/react';
interface ToggleProps {
enabled: boolean;
setEnabled: (value: boolean) => void;
addOverride: (value: string | undefined, pos: number) => void;
pos: number;
addOverride: (value: string | undefined, id: string) => void;
id: string;
}
/**
@@ -13,18 +13,18 @@ interface ToggleProps {
* @param {boolean} obj.enabled - whether the toggle is turned on or off
* @param {function} obj.setEnabled - change the state of the toggle
* @param {function} obj.addOverride - a function that adds an override to a certain secret
* @param {number} obj.pos - position of a certain secret
* @param {number} obj.id - id of a certain secret
* @returns
*/
const Toggle = ({ enabled, setEnabled, addOverride, pos }: ToggleProps): JSX.Element => {
const Toggle = ({ enabled, setEnabled, addOverride, id }: ToggleProps): JSX.Element => {
return (
<Switch
checked={enabled}
onChange={() => {
if (enabled === false) {
addOverride('', pos);
addOverride('', id);
} else {
addOverride(undefined, pos);
addOverride(undefined, id);
}
setEnabled(!enabled);
}}
@@ -1,7 +1,5 @@
import { Fragment } from 'react';
import { useRouter } from 'next/router';
import { Dialog, Transition } from '@headlessui/react';
import { plans } from 'public/data/frequentConstants';
import Button from '../buttons/Button';
import InputField from '../InputField';
@@ -12,7 +10,6 @@ type Props = {
submitModal: (email: string) => void;
email: string;
setEmail: (email: string) => void;
currentPlan: string;
orgName: string;
};
@@ -22,13 +19,11 @@ const AddUserDialog = ({
submitModal,
email,
setEmail,
currentPlan,
orgName,
}: Props) => {
const submit = () => {
submitModal(email);
};
const router = useRouter();
return (
<div className='z-50'>
@@ -81,28 +76,6 @@ const AddUserDialog = ({
isRequired
/>
</div>
{currentPlan === plans.starter && (
<div className='flex flex-row'>
<button
type='button'
className='inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
onClick={() =>
router.push(`/settings/billing/${ router.query.id}`)
}
>
You can add up to 5 members on the Starter plan.
</button>
<button
type='button'
className='ml-1 inline-flex justify-center rounded-md py-1 underline underline-offset-2 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
onClick={() =>
router.push(`/settings/billing/${ router.query.id}`)
}
>
Upgrade now.
</button>
</div>
)}
<div className='mt-4 max-w-max'>
<Button
onButtonPressed={submit}
@@ -45,19 +45,19 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
{t('dashboard:sidebar.delete-key-dialog.title')}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
<p className="text-sm text-bunker-300">
{t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')}
</p>
</div>
<div className="mt-6 flex justify-start">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-red-700 hover:bg-red-600 px-4 py-2 text-sm font-medium text-bunker-200 hover:text-white text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
>
Delete
@@ -36,7 +36,7 @@ const Notification = ({ notification, clearNotification }: NotificationProps) =>
return (
<div
className="relative w-full flex items-center justify-between px-6 py-6 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3"
className="relative w-full flex items-center justify-between px-6 py-4 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3"
role="alert"
>
{notification.type === 'error' && (
@@ -13,7 +13,7 @@ import { Tag } from 'public/data/frequentInterfaces';
* @param {function} obj.modifyTags - modify tags for a certain secret
* @param {Tag[]} obj.position - currently selected tags for a certain secret
*/
const AddTagsMenu = ({ allTags, currentTags, modifyTags, position }: { allTags: Tag[]; currentTags: Tag[]; modifyTags: (value: Tag[], position: number) => void; position: number; }) => {
const AddTagsMenu = ({ allTags, currentTags, modifyTags, id }: { allTags: Tag[]; currentTags: Tag[]; modifyTags: (value: Tag[], id: string) => void; id: string; }) => {
const router = useRouter();
return (
<Menu as="div" className="ml-2 relative inline-block text-left">
@@ -41,7 +41,7 @@ const AddTagsMenu = ({ allTags, currentTags, modifyTags, position }: { allTags:
<button
type="button"
className={`${currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? "opacity-30 cursor-default" : "hover:bg-mineshaft-700"} w-full text-left bg-mineshaft-800 px-2 py-0.5 text-bunker-200 rounded-sm flex items-center`}
onClick={() => {if (!currentTags?.map(currentTag => currentTag.name).includes(tag.name)) {modifyTags(currentTags.concat([tag]), position)}}}
onClick={() => {if (!currentTags?.map(currentTag => currentTag.name).includes(tag.name)) {modifyTags(currentTags.concat([tag]), id)}}}
>
{currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? <FontAwesomeIcon icon={faCheckSquare} className="text-xs mr-2 text-primary"/> : <FontAwesomeIcon icon={faSquare} className="text-xs mr-2"/>} {tag.name}
</button>
@@ -6,11 +6,11 @@ import { useTranslation } from 'next-i18next';
const CommentField = ({
comment,
modifyComment,
position
id
}: {
comment: string;
modifyComment: (value: string, posistion: number) => void;
position: number;
modifyComment: (value: string, id: string) => void;
id: string;
}) => {
const { t } = useTranslation();
@@ -20,7 +20,7 @@ const CommentField = ({
<textarea
className="placeholder:text-bunker-400 dark:[color-scheme:dark] h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
value={comment}
onChange={(e) => modifyComment(e.target.value, position)}
onChange={(e) => modifyComment(e.target.value, id)}
placeholder="Leave any comments here..."
/>
</div>
@@ -9,15 +9,15 @@ import { PopoverObject } from '../v2/Popover/Popover';
const REGEX = /([$]{.*?})/g;
interface DashboardInputFieldProps {
position: number;
onChangeHandler: (value: string, position: number) => void;
id: string;
onChangeHandler: (value: string, id: string) => void;
value: string | undefined;
type: 'varName' | 'value' | 'comment';
blurred?: boolean;
isDuplicate?: boolean;
isCapitalized?: boolean;
overrideEnabled?: boolean;
modifyValueOverride?: (value: string | undefined, position: number) => void;
modifyValueOverride?: (value: string | undefined, id: string) => void;
isSideBarOpen?: boolean;
}
@@ -37,7 +37,7 @@ interface DashboardInputFieldProps {
*/
const DashboardInputField = ({
position,
id,
onChangeHandler,
type,
value,
@@ -72,7 +72,7 @@ const DashboardInputField = ({
}`}
>
<input
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, position)}
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)}
type={type}
value={value}
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${
@@ -105,9 +105,9 @@ const DashboardInputField = ({
<button type="button" onClick={() => {
if (modifyValueOverride) {
if (overrideEnabled === false) {
modifyValueOverride('', position);
modifyValueOverride('', id);
} else {
modifyValueOverride(undefined, position);
modifyValueOverride(undefined, id);
}
}
}}>
@@ -126,7 +126,7 @@ const DashboardInputField = ({
const error = startsWithNumber || isDuplicate;
return (
<PopoverObject text={value || ''} onChangeHandler={onChangeHandler} position={position}>
<PopoverObject text={value || ''} onChangeHandler={onChangeHandler} id={id}>
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
isSideBarOpen && 'bg-mineshaft-700 duration-200'
}`}>
@@ -157,7 +157,7 @@ const DashboardInputField = ({
)}
<input
value={value}
onChange={(e) => onChangeHandler(e.target.value, position)}
onChange={(e) => onChangeHandler(e.target.value, id)}
onScroll={syncScroll}
className={`${
blurred
@@ -175,10 +175,10 @@ const DashboardInputField = ({
} ${overrideEnabled ? 'text-primary-300' : 'text-gray-400'}
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
>
{value?.split(REGEX).map((word, id) => {
{value?.split(REGEX).map((word) => {
if (word.match(REGEX) !== null) {
return (
<span className="ph-no-capture text-yellow" key={`${word}.${id + 1}`}>
<span className="ph-no-capture text-yellow" key={id}>
{word.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)}
@@ -231,7 +231,7 @@ function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInput
return (
prev.value === next.value &&
prev.type === next.type &&
prev.position === next.position &&
prev.id === next.id &&
prev.blurred === next.blurred &&
prev.isCapitalized === next.isCapitalized &&
prev.overrideEnabled === next.overrideEnabled &&
@@ -8,6 +8,7 @@ import { parseDocument, Scalar, YAMLMap } from 'yaml';
import Button from '../basic/buttons/Button';
import Error from '../basic/Error';
import { useNotificationContext } from '../context/Notifications/NotificationProvider';
import { parseDotEnv } from '../utilities/parseDotEnv';
import guidGenerator from '../utilities/randomId';
@@ -32,6 +33,7 @@ const DropZone = ({
numCurrentRows
}: DropZoneProps) => {
const { t } = useTranslation();
const { createNotification } = useNotificationContext();
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
@@ -110,6 +112,15 @@ const DropZone = ({
const file = e.dataTransfer.files[0];
const reader = new FileReader();
if (file === undefined) {
createNotification({
text: `You can't inject files from VS Code. Click 'Reveal in finder', and drag your file directly from the directory where it's located.`,
type: 'error',
timeoutMs: 10000
});
setLoading(false);
return;
}
const fileType = file.name.split('.')[1];
reader.onload = (event) => {
@@ -10,10 +10,10 @@ import { Menu, Transition } from '@headlessui/react';
*/
const GenerateSecretMenu = ({
modifyValue,
position
id
}: {
modifyValue: (value: string, position: number) => void;
position: number;
modifyValue: (value: string, id: string) => void;
id: string;
}) => {
const [randomStringLength, setRandomStringLength] = useState(32);
const { t } = useTranslation();
@@ -51,7 +51,7 @@ const GenerateSecretMenu = ({
[...Array(randomStringLength)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join(''),
position
id
);
}
}}
+10 -10
View File
@@ -8,11 +8,11 @@ import { DeleteActionButton } from './DeleteActionButton';
interface KeyPairProps {
keyPair: SecretDataProps;
modifyKey: (value: string, position: number) => void;
modifyValue: (value: string, position: number) => void;
modifyValueOverride: (value: string | undefined, position: number) => void;
modifyComment: (value: string, position: number) => void;
modifyTags: (value: Tag[], position: number) => void;
modifyKey: (value: string, id: string) => void;
modifyValue: (value: string, id: string) => void;
modifyValueOverride: (value: string | undefined, id: string) => void;
modifyComment: (value: string, id: string) => void;
modifyTags: (value: Tag[], id: string) => void;
isBlurred: boolean;
isDuplicate: boolean;
toggleSidebar: (id: string) => void;
@@ -108,7 +108,7 @@ const KeyPair = ({
isCapitalized = {isCapitalized}
onChangeHandler={modifyKey}
type="varName"
position={keyPair.pos}
id={keyPair.id}
value={keyPair.key}
isDuplicate={isDuplicate}
overrideEnabled={keyPair.valueOverride !== undefined}
@@ -124,7 +124,7 @@ const KeyPair = ({
<DashboardInputField
onChangeHandler={keyPair.valueOverride !== undefined ? modifyValueOverride : modifyValue}
type="value"
position={keyPair.pos}
id={keyPair.id}
value={keyPair.valueOverride !== undefined ? keyPair.valueOverride : keyPair.value}
blurred={isBlurred}
overrideEnabled={keyPair.valueOverride !== undefined}
@@ -137,7 +137,7 @@ const KeyPair = ({
<DashboardInputField
onChangeHandler={modifyComment}
type="comment"
position={keyPair.pos}
id={keyPair.id}
value={keyPair.comment}
isDuplicate={isDuplicate}
isSideBarOpen={keyPair.id === sidebarSecretId}
@@ -149,11 +149,11 @@ const KeyPair = ({
{keyPair.tags?.map((tag, index) => (
index < 2 && <div key={keyPair.pos} className={`ml-2 px-1.5 ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.color} rounded-sm text-sm ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.colorText} flex items-center`}>
<span className='mb-0.5 cursor-default'>{tag.name}</span>
<FontAwesomeIcon icon={faXmark} className="ml-1 cursor-pointer p-1" onClick={() => modifyTags(keyPair.tags.filter(ttag => ttag._id !== tag._id), keyPair.pos)}/>
<FontAwesomeIcon icon={faXmark} className="ml-1 cursor-pointer p-1" onClick={() => modifyTags(keyPair.tags.filter(ttag => ttag._id !== tag._id), keyPair.id)}/>
</div>
))}
<AddTagsMenu allTags={tags} currentTags={keyPair.tags} modifyTags={modifyTags} position={keyPair.pos} />
<AddTagsMenu allTags={tags} currentTags={keyPair.tags} modifyTags={modifyTags} id={keyPair.id} />
</div>
</div>
<div
+11 -11
View File
@@ -33,10 +33,10 @@ export interface DeleteRowFunctionProps {
interface SideBarProps {
toggleSidebar: (value: string) => void;
data: SecretProps[];
modifyKey: (value: string, position: number) => void;
modifyValue: (value: string, position: number) => void;
modifyValueOverride: (value: string | undefined, position: number) => void;
modifyComment: (value: string, position: number) => void;
modifyKey: (value: string, id: string) => void;
modifyValue: (value: string, id: string) => void;
modifyValueOverride: (value: string | undefined, id: string) => void;
modifyComment: (value: string, id: string) => void;
buttonReady: boolean;
savePush: () => void;
sharedToHide: string[];
@@ -110,7 +110,7 @@ const SideBar = ({
<DashboardInputField
onChangeHandler={modifyKey}
type="varName"
position={data[0]?.pos}
id={data[0]?.id}
value={data[0]?.key}
isDuplicate={false}
blurred={false}
@@ -128,14 +128,14 @@ const SideBar = ({
<DashboardInputField
onChangeHandler={modifyValue}
type="value"
position={data[0].pos}
id={data[0].id}
value={data[0]?.value}
isDuplicate={false}
blurred
/>
</div>
<div className="absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50">
<GenerateSecretMenu modifyValue={modifyValue} position={data[0]?.pos} />
<GenerateSecretMenu modifyValue={modifyValue} id={data[0]?.id} />
</div>
</div>
) : (
@@ -154,7 +154,7 @@ const SideBar = ({
enabled={overrideEnabled}
setEnabled={setOverrideEnabled}
addOverride={modifyValueOverride}
pos={data[0]?.pos}
id={data[0]?.id}
/>
</div>
)}
@@ -167,14 +167,14 @@ const SideBar = ({
<DashboardInputField
onChangeHandler={modifyValueOverride}
type="value"
position={data[0]?.pos}
id={data[0]?.id}
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
isDuplicate={false}
blurred
/>
</div>
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
<GenerateSecretMenu modifyValue={modifyValueOverride} position={data[0]?.pos} />
<GenerateSecretMenu modifyValue={modifyValueOverride} id={data[0]?.id} />
</div>
</div>
</div>
@@ -182,7 +182,7 @@ const SideBar = ({
<CommentField
comment={data[0]?.comment}
modifyComment={modifyComment}
position={data[0]?.pos}
id={data[0]?.id}
/>
</div>
)}
@@ -5,13 +5,13 @@ import * as Popover from '@radix-ui/react-popover';
type Props = {
children: any;
text: string;
onChangeHandler: (value: string, position: number) => void;
position: number;
onChangeHandler: (value: string, id: string) => void;
id: string;
};
export type PopoverProps = Props;
export const PopoverObject = ({children, text, onChangeHandler, position}: Props) => (
export const PopoverObject = ({children, text, onChangeHandler, id}: Props) => (
<Popover.Root>
<Popover.Trigger asChild className='data-[state=open]:outline data-[state=open]:outline-primary data-[state=closed]:hover:outline data-[state=closed]:hover:outline-mineshaft-400'>
{children}
@@ -26,7 +26,7 @@ export const PopoverObject = ({children, text, onChangeHandler, position}: Props
<div className="flex flex-col pt-2 dark">
<p className="text-bunker-200 text-[15px] leading-[0px] font-medium mb-5">Comment</p>
<textarea
onChange={(e) => onChangeHandler(e.target.value, position)}
onChange={(e) => onChangeHandler(e.target.value, id)}
// type={type}
value={text}
className='z-10 dark:[color-scheme:dark] peer h-[20rem] ph-no-capture bg-bunker-600 border border-mineshaft-500 rounded-md py-2.5 caret-bunker-200 text-sm px-2 w-full outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'
+159
View File
@@ -0,0 +1,159 @@
import { useState } from 'react';
import { faArrowUpRightFromSquare, faCheck, faClipboard } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as Tabs from '@radix-ui/react-tabs';
export type TabsProps = Tabs.TabsProps & {};
function copyToClipboard(id: string, setState: (value: boolean) => void) {
// Get the text field
const copyText = document.getElementById(id) as HTMLInputElement;
// Select the text field
copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.value);
setState(true);
setTimeout(() => setState(false), 2000);
// Alert the copied text
// alert("Copied the text: " + copyText.value);
}
const CodeItem = ({isCopied, setIsCopied, textExplanation, code, id}: {isCopied: boolean, setIsCopied: (value: boolean) => void, textExplanation: string, code: string, id: string}) => {
return <>
<p className="mb-2 mt-4 text-bunker-300 text-sm leading-normal">
{textExplanation}
</p>
<div className='font-mono text-sm px-3 py-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between'>
<input disabled value={code} id={id} className='w-full bg-transparent text-bunker-200'/>
<button
type="button"
onClick={() => copyToClipboard(id, setIsCopied)}
className='h-full pl-3.5 pr-2 text-bunker-300 hover:text-primary-200 duration-200'
>
{isCopied ? (
<FontAwesomeIcon
icon={faCheck}
className='pr-0.5'
/>
) : (
<FontAwesomeIcon icon={faClipboard} />
)}
</button>
</div>
</>
}
export const TabsObject = () => {
const [downloadCodeCopied, setDownloadCodeCopied] = useState(false);
const [downloadCode2Copied, setDownloadCode2Copied] = useState(false);
const [loginCodeCopied, setLoginCodeCopied] = useState(false);
const [initCodeCopied, setInitCodeCopied] = useState(false);
const [runCodeCopied, setRunCodeCopied] = useState(false);
return <Tabs.Root
className="flex flex-col w-full cursor-default border border-mineshaft-600 rounded-md"
defaultValue="tab1"
>
<Tabs.List className="shrink-0 flex border-b border-mineshaft-600" aria-label="Manage your account">
<Tabs.Trigger
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
value="tab1"
>
MacOS
</Tabs.Trigger>
<Tabs.Trigger
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
value="tab2"
>
Windows
</Tabs.Trigger>
<Tabs.Trigger
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
value="tab3"
>
Arch Linux
</Tabs.Trigger>
<a
target='_blank'
rel="noopener noreferrer"
className="bg-bunker-700 hover:text-bunker-100 duration-200 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
href='https://infisical.com/docs/cli/overview'
>
Other Platforms <FontAwesomeIcon icon={faArrowUpRightFromSquare} className='ml-2'/>
</a>
</Tabs.List>
<Tabs.Content
className="grow p-5 bg-bunker-700 rounded-b-md outline-none cursor-default"
value="tab1"
>
<CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCode" />
<CodeItem isCopied={loginCodeCopied} setIsCopied={setLoginCodeCopied} textExplanation="2. Login" code="infisical login" id="loginCode" />
<CodeItem isCopied={initCodeCopied} setIsCopied={setInitCodeCopied} textExplanation="3. Choose Project" code="infisical init" id="initCode" />
<CodeItem isCopied={runCodeCopied} setIsCopied={setRunCodeCopied} textExplanation="4. Done! Now, you can prepend your usual start script with:" code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]" id="runCode" />
<p className='text-bunker-300 text-sm mt-2'>You can find example of start commands for different frameworks <a
className='text-primary underline underline-offset-2'
target="_blank"
rel="noopener noreferrer"
href='https://infisical.com/docs/integrations/overview'
>
here
</a>. </p>
</Tabs.Content>
<Tabs.Content
className="grow p-5 bg-bunker-700 rounded-b-md outline-none"
value="tab2"
>
<CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCodeW" />
<div className='font-mono text-sm px-3 py-2 mt-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between'>
<input disabled value="scoop install infisical" id="downloadCodeW2" className='w-full bg-transparent text-bunker-200'/>
<button
type="button"
onClick={() => copyToClipboard("downloadCodeW2", setDownloadCode2Copied)}
className='h-full pl-3.5 pr-2 text-bunker-300 hover:text-primary-200 duration-200'
>
{downloadCode2Copied ? (
<FontAwesomeIcon
icon={faCheck}
className='pr-0.5'
/>
) : (
<FontAwesomeIcon icon={faClipboard} />
)}
</button>
</div>
<CodeItem isCopied={loginCodeCopied} setIsCopied={setLoginCodeCopied} textExplanation="2. Login" code="infisical login" id="loginCodeW" />
<CodeItem isCopied={initCodeCopied} setIsCopied={setInitCodeCopied} textExplanation="3. Choose Project" code="infisical init" id="initCodeW" />
<CodeItem isCopied={runCodeCopied} setIsCopied={setRunCodeCopied} textExplanation="4. Done! Now, you can prepend your usual start script with:" code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]" id="runCodeW" />
<p className='text-bunker-300 text-sm mt-2'>You can find example of start commands for different frameworks <a
className='text-primary underline underline-offset-2'
target="_blank"
rel="noopener noreferrer"
href='https://infisical.com/docs/integrations/overview'
>
here
</a>. </p>
</Tabs.Content>
<Tabs.Content
className="grow p-5 bg-bunker-700 rounded-b-md outline-none cursor-default"
value="tab3"
>
<CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCodeL" />
<CodeItem isCopied={loginCodeCopied} setIsCopied={setLoginCodeCopied} textExplanation="2. Login" code="infisical login" id="loginCodeL" />
<CodeItem isCopied={initCodeCopied} setIsCopied={setInitCodeCopied} textExplanation="3. Choose Project" code="infisical init" id="initCodeL" />
<CodeItem isCopied={runCodeCopied} setIsCopied={setRunCodeCopied} textExplanation="4. Done! Now, you can prepend your usual start script with:" code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]" id="runCodeL" />
<p className='text-bunker-300 text-sm mt-2'>You can find example of start commands for different frameworks <a
className='text-primary underline underline-offset-2'
target="_blank"
rel="noopener noreferrer"
href='https://infisical.com/docs/integrations/overview'
>
here
</a>. </p>
</Tabs.Content>
</Tabs.Root>
};
@@ -0,0 +1,4 @@
export type {
TabsProps
} from './Tabs';
export { TabsObject } from './Tabs';
+1 -1
View File
@@ -45,4 +45,4 @@ const plansProd: Mapping = {
export const plans = plansProd || plansDev;
export const leaveConfirmDefaultMessage = 'Do you want to save your results before leaving this page?';
export const leaveConfirmDefaultMessage = 'Your changes will be lost if you leave the page. Are you sure you want to continue?';
@@ -0,0 +1,38 @@
import { apiRequest } from "@app/config/request";
interface RequestType {
method: string;
secret: {
type: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
tags: string[];
}
}
const batchSecrets = async ({
workspaceId,
environment,
requests
}: {
workspaceId: string;
environment: string;
requests: RequestType[];
}) => {
const { data } = await apiRequest.post('/api/v2/secrets/batch', {
workspaceId,
environment,
requests
});
return data;
}
export default batchSecrets;
+365 -179
View File
@@ -30,6 +30,7 @@ import DropZone from '@app/components/dashboard/DropZone';
import KeyPair from '@app/components/dashboard/KeyPair';
import SideBar from '@app/components/dashboard/SideBar';
import NavHeader from '@app/components/navigation/NavHeader';
import { decryptAssymmetric, decryptSymmetric } from '@app/components/utilities/cryptography/crypto';
import guidGenerator from '@app/components/utilities/randomId';
import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
import getSecretsForProject from '@app/components/utilities/secrets/getSecretsForProject';
@@ -41,12 +42,14 @@ import performSecretRollback from '@app/ee/api/secrets/PerformSecretRollback';
import PITRecoverySidebar from '@app/ee/components/PITRecoverySidebar';
import { useLeaveConfirm } from '@app/hooks';
import addSecrets from '../api/files/AddSecrets';
import deleteSecrets from '../api/files/DeleteSecrets';
import updateSecrets from '../api/files/UpdateSecrets';
// import addSecrets from '../api/files/AddSecrets';
// import deleteSecrets from '../api/files/DeleteSecrets';
// import updateSecrets from '../api/files/UpdateSecrets';
import batchSecrets from '../api/files/batchSecrets';
import getUser from '../api/user/getUser';
import checkUserAction from '../api/userActions/checkUserAction';
import registerUserAction from '../api/userActions/registerUserAction';
import getLatestFileKey from '../api/workspace/getLatestFileKey';
import getWorkspaceEnvironments from '../api/workspace/getWorkspaceEnvironments';
import getWorkspaces from '../api/workspace/getWorkspaces';
import getWorkspaceTags from '../api/workspace/getWorkspaceTags';
@@ -94,6 +97,32 @@ interface SnapshotProps {
}[];
}
interface EncryptedSecretProps {
_id: string;
createdAt: string;
environment: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
type: 'personal' | 'shared';
tags: Tag[];
}
interface SecretProps {
key: string;
value: string | undefined;
type: 'personal' | 'shared';
comment: string;
id: string;
tags: Tag[];
}
/**
* this function finds the teh duplicates in an array
* @param arr - array of anything (e.g., with secret keys and types (personal/shared))
@@ -302,50 +331,50 @@ export default function Dashboard() {
);
};
const modifyValue = (value: string, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, value } : e)));
const modifyValue = (value: string, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, value } : e)));
setHasUnsavedChanges(true);
};
const modifyValueOverride = (valueOverride: string | undefined, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, valueOverride } : e)));
const modifyValueOverride = (valueOverride: string | undefined, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, valueOverride } : e)));
setHasUnsavedChanges(true);
};
const modifyKey = (key: string, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, key } : e)));
const modifyKey = (key: string, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, key } : e)));
setHasUnsavedChanges(true);
};
const modifyComment = (comment: string, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, comment } : e)));
const modifyComment = (comment: string, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, comment } : e)));
setHasUnsavedChanges(true);
};
const modifyTags = (tags: Tag[], pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, tags } : e)));
const modifyTags = (tags: Tag[], id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, tags } : e)));
setHasUnsavedChanges(true);
};
// For speed purposes and better perforamance, we are using useCallback
const listenChangeValue = useCallback((value: string, pos: number) => {
modifyValue(value, pos);
const listenChangeValue = useCallback((value: string, id: string) => {
modifyValue(value, id);
}, []);
const listenChangeValueOverride = useCallback((value: string | undefined, pos: number) => {
modifyValueOverride(value, pos);
const listenChangeValueOverride = useCallback((value: string | undefined, id: string) => {
modifyValueOverride(value, id);
}, []);
const listenChangeKey = useCallback((value: string, pos: number) => {
modifyKey(value, pos);
const listenChangeKey = useCallback((value: string, id: string) => {
modifyKey(value, id);
}, []);
const listenChangeComment = useCallback((value: string, pos: number) => {
modifyComment(value, pos);
const listenChangeComment = useCallback((value: string, id: string) => {
modifyComment(value, id);
}, []);
const listenChangeTags = useCallback((value: Tag[], pos: number) => {
modifyTags(value, pos);
const listenChangeTags = useCallback((value: Tag[], id: string) => {
modifyTags(value, id);
}, []);
/**
@@ -353,174 +382,331 @@ export default function Dashboard() {
*/
// TODO(akhilmhdh): split and make it small
const savePush = async (dataToPush?: SecretDataProps[]) => {
setSaveLoading(true);
let newData: SecretDataProps[] | null | undefined;
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
if ((dataToPush ?? [])?.length > 0) {
newData = dataToPush;
} else {
newData = data;
}
try {
setSaveLoading(true);
let newData: SecretDataProps[] | null | undefined;
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
if ((dataToPush ?? [])?.length > 0) {
newData = dataToPush;
} else {
newData = data;
}
// Checking if any of the secret keys start with a number - if so, don't do anything
const nameErrors = !newData!
.map((secret) => !Number.isNaN(Number(secret.key.charAt(0))))
.every((v) => v === false);
const duplicatesExist =
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
// Checking if any of the secret keys start with a number - if so, don't do anything
const nameErrors = !newData!
.map((secret) => !Number.isNaN(Number(secret.key.charAt(0))))
.every((v) => v === false);
const duplicatesExist =
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
if (nameErrors) {
setSaveLoading(false);
return createNotification({
text: 'Solve all name errors before saving secrets.',
type: 'error'
});
}
if (nameErrors) {
setSaveLoading(false);
return createNotification({
text: 'Solve all name errors before saving secrets.',
type: 'error'
});
}
if (duplicatesExist) {
setSaveLoading(false);
return createNotification({
text: 'Remove duplicated secret names before saving.',
type: 'error'
});
}
if (duplicatesExist) {
setSaveLoading(false);
return createNotification({
text: 'Remove duplicated secret names before saving.',
type: 'error'
});
}
if (selectedEnv?.isWriteDenied) {
setSaveLoading(false);
return createNotification({
text: 'You are not allowed to edit this environment',
type: 'error'
});
}
if (selectedEnv?.isWriteDenied) {
setSaveLoading(false);
return createNotification({
text: 'You are not allowed to edit this environment',
type: 'error'
});
}
// Once "Save changes" is clicked, disable that button
setHasUnsavedChanges(false);
// Once "Save changes" is clicked, disable that button
setHasUnsavedChanges(false);
const secretsToBeDeleted = initialData!
.filter(
(initDataPoint) =>
!newData!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => secret.id);
console.log('delete', secretsToBeDeleted.length);
const secretsToBeAdded = newData!.filter(
(newDataPoint) =>
!initialData!.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
);
console.log('add', secretsToBeAdded.length);
const secretsToBeUpdated = newData!.filter((newDataPoint) =>
initialData!
.filter(
(initDataPoint) =>
newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !==
initDataPoint.value ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].comment !==
initDataPoint.comment) ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !==
initDataPoint?.tags
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
);
console.log('update', secretsToBeUpdated.length);
const newOverrides = newData!.filter(
(newDataPoint) => newDataPoint.valueOverride !== undefined
);
const initOverrides = initialData!.filter(
(initDataPoint) => initDataPoint.valueOverride !== undefined
);
const overridesToBeDeleted = initOverrides
.filter(
(initDataPoint) =>
!newOverrides!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => String(secret.idOverride));
console.log('override delete', overridesToBeDeleted.length);
const overridesToBeAdded = newOverrides!
.filter(
(newDataPoint) =>
!initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override add', overridesToBeAdded.length);
const overridesToBeUpdated = newOverrides!
.filter((newDataPoint) =>
initOverrides
const secretsToBeDeleted = initialData!
.filter(
(initDataPoint) =>
newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.valueOverride !== initDataPoint.valueOverride ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.comment !== initDataPoint.comment ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !== initDataPoint?.tags)
!newData!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override update', overridesToBeUpdated.length);
.map((secret) => secret.id);
console.log('delete', secretsToBeDeleted.length);
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) });
}
if (selectedEnv && secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded),
workspaceId,
env: selectedEnv.slug
});
if (secrets) await addSecrets({ secrets, env: selectedEnv.slug, workspaceId });
}
if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated),
workspaceId,
env: selectedEnv.slug
});
if (secrets) await updateSecrets({ secrets });
}
const secretsToBeAdded = newData!.filter(
(newDataPoint) =>
!initialData!.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
);
console.log('add', secretsToBeAdded.length);
setInitialData(structuredClone(newData));
const secretsToBeUpdated = newData!.filter((newDataPoint) =>
initialData!
.filter(
(initDataPoint) =>
newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !==
initDataPoint.value ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].comment !==
initDataPoint.comment ||
JSON.stringify(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !==
JSON.stringify(initDataPoint?.tags))
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
);
console.log('update', secretsToBeUpdated.length);
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: 'first_time_secrets_pushed' });
}
const newOverrides = newData!.filter(
(newDataPoint) => newDataPoint.valueOverride !== undefined
);
const initOverrides = initialData!.filter(
(initDataPoint) => initDataPoint.valueOverride !== undefined
);
// increasing the number of project commits
setNumSnapshots((numSnapshots ?? 0) + 1);
setSaveLoading(false);
const overridesToBeDeleted = initOverrides
.filter(
(initDataPoint) =>
!newOverrides!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => String(secret.idOverride));
console.log('override delete', overridesToBeDeleted.length);
const overridesToBeAdded = newOverrides!
.filter(
(newDataPoint) =>
!initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override add', overridesToBeAdded.length);
const overridesToBeUpdated = newOverrides!
.filter((newDataPoint) =>
initOverrides
.filter(
(initDataPoint) =>
newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.valueOverride !== initDataPoint.valueOverride ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.comment !== initDataPoint.comment ||
JSON.stringify(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !==
JSON.stringify(initDataPoint?.tags))
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override update', overridesToBeUpdated.length);
const requests: any = []; // TODO: fix any
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
secretsToBeDeleted.concat(overridesToBeDeleted).forEach((_id: string) => {
requests.push({
method: 'DELETE',
secret: {
_id
}
});
});
}
if (selectedEnv && secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded),
workspaceId,
env: selectedEnv.slug
});
if (secrets) {
secrets.forEach((secret) => {
requests.push({
method: 'POST',
secret: {
type: secret.type,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
tags: secret.tags
}
})
});
}
}
if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated),
workspaceId,
env: selectedEnv.slug
});
if (secrets) {
secrets.forEach((secret) => {
requests.push({
method: 'PATCH',
secret: {
_id: secret.id,
type: secret.type,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
tags: secret.tags
}
});
});
}
}
let newSecrets;
if (selectedEnv && requests.length > 0) {
newSecrets = await batchSecrets({
workspaceId,
environment: selectedEnv.slug,
requests
});
}
let formattedNewDecryptedKeys;
if (newSecrets.createdSecrets) {
const latestKey = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
const tempDecryptedSecrets: SecretProps[] = [];
if (latestKey) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: latestKey.latestKey.encryptedKey,
nonce: latestKey.latestKey.nonce,
publicKey: latestKey.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
// decrypt secret keys, values, and comments
newSecrets.createdSecrets.forEach((secret: EncryptedSecretProps) => {
const plainTextKey = decryptSymmetric({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
});
let plainTextValue;
if (secret.secretValueCiphertext !== undefined) {
plainTextValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
});
} else {
plainTextValue = undefined;
}
let plainTextComment;
if (secret.secretCommentCiphertext) {
plainTextComment = decryptSymmetric({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key
});
} else {
plainTextComment = '';
}
tempDecryptedSecrets.push({
id: secret._id,
key: plainTextKey,
value: plainTextValue,
type: secret.type,
comment: plainTextComment,
tags: secret.tags
});
});
}
const secretKeys = [...new Set(tempDecryptedSecrets.map((secret) => secret.key))];
formattedNewDecryptedKeys = secretKeys.map((key, index) => ({
id: tempDecryptedSecrets.filter((secret) => secret.key === key && secret.type === 'shared')[0]
?.id,
idOverride: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'personal'
)[0]?.id,
pos: (newData?.filter(dp => !dp.id.includes('-'))?.length ?? 0) + index,
key,
value: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'shared'
)[0]?.value,
valueOverride: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'personal'
)[0]?.value,
comment: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'shared'
)[0]?.comment,
tags: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'shared'
)[0]?.tags
}));
setInitialData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id))));
setData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id))))
} else {
setInitialData(structuredClone(newData));
}
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: 'first_time_secrets_pushed' });
}
// increasing the number of project commits
setNumSnapshots((numSnapshots ?? 0) + 1);
setSaveLoading(false);
createNotification({
text: `Successfully saved secrets.`,
type: 'success'
});
} catch (error) {
console.log("Something went wrong while saving secrets: ", error)
createNotification({
text: `Something went wrong while saving secrets.`,
type: 'error'
});
}
return undefined;
};
@@ -820,7 +1006,7 @@ export default function Dashboard() {
</div>
</div>
<div className="w-[calc(10%)] border-r border-mineshaft-600">
<div className="flex items-center max-h-16">
<div className="flex items-center max-h-16 overflow-hidden">
<div className='text-bunker-300 px-2 font-semibold h-10 flex items-center w-3/12'>Comment</div>
</div>
</div>
+40 -22
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import Head from 'next/head';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { faSlack } from '@fortawesome/free-brands-svg-icons';
@@ -16,6 +17,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import onboardingCheck from '@app/components/utilities/checks/OnboardingCheck';
import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps';
import { TabsObject } from '@app/components/v2/Tabs';
import registerUserAction from '../api/userActions/registerUserAction';
@@ -43,7 +45,7 @@ const learningItem = ({
<a
target={`${link.includes('https') ? '_blank' : '_self'}`}
rel="noopener noreferrer"
className="w-full"
className={`w-full ${complete && 'opacity-30 hover:opacity-100 duration-200'}`}
href={link}
>
<div
@@ -57,13 +59,13 @@ const learningItem = ({
});
}
}}
className="relative bg-bunker-700 hover:bg-bunker-500 shadow-xl duration-200 rounded-md border border-dashed border-bunker-400 pl-2 pr-6 py-2 h-[5.5rem] w-full flex items-center justify-between overflow-hidden my-1.5 cursor-pointer"
className="relative group bg-bunker-500 hover:bg-mineshaft-700 shadow-xl duration-200 rounded-md border border-mineshaft-600 pl-2 pr-6 py-2 h-[5.5rem] w-full flex items-center justify-between overflow-hidden mb-3 cursor-pointer"
>
<div className="flex flex-row items-center mr-4">
<FontAwesomeIcon icon={icon} className="text-4xl mx-2 w-16" />
{complete && (
<div className="bg-bunker-700 w-7 h-7 rounded-full absolute left-12 top-10 p-2 flex items-center justify-center">
<FontAwesomeIcon icon={faCheckCircle} className="text-4xl w-5 h-5 text-green" />
<div className="bg-bunker-500 group-hover:bg-mineshaft-700 w-7 h-7 rounded-full absolute left-12 top-10 p-2 flex items-center justify-center">
<FontAwesomeIcon icon={faCheckCircle} className="text-4xl w-5 h-5 text-primary" />
</div>
)}
<div className="flex flex-col items-start">
@@ -72,11 +74,11 @@ const learningItem = ({
</div>
</div>
<div
className={`pr-4 font-semibold text-sm w-28 text-right ${complete && 'text-green'}`}
className={`pr-4 font-semibold text-sm w-28 text-right ${complete && 'text-primary'}`}
>
{complete ? 'Complete!' : `About ${time}`}
</div>
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />}
</div>
</a>
);
@@ -101,7 +103,7 @@ const learningItem = ({
<div className="bg-bunker-700 w-7 h-7 rounded-full absolute left-11 top-10">
<FontAwesomeIcon
icon={faCheckCircle}
className="absolute text-4xl left-12 top-16 w-5 h-5 text-green"
className="absolute text-4xl left-12 top-16 w-5 h-5 text-primary"
/>
</div>
)}
@@ -110,10 +112,10 @@ const learningItem = ({
<div className="text-sm font-normal mt-0.5">{subText}</div>
</div>
</div>
<div className={`pr-4 font-semibold text-sm w-28 text-right ${complete && 'text-green'}`}>
<div className={`pr-4 font-semibold text-sm w-28 text-right ${complete && 'text-primary'}`}>
{complete ? 'Complete!' : `About ${time}`}
</div>
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />}
</div>
);
};
@@ -142,16 +144,17 @@ export default function Home() {
}, []);
return (
<div className="mx-6 lg:mx-0 w-full overflow-y-scroll pt-4">
<div className="mx-6 lg:mx-0 w-full pt-4">
<Head>
<title>Infisical Guide</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-col items-center text-gray-300 text-lg mx-auto max-w-2xl lg:max-w-3xl xl:max-w-4xl py-6">
<div className="text-3xl font-bold text-left w-full">Your quick start guide</div>
<div className="text-md text-left w-full pt-2 pb-4 text-bunker-300">
<div className="flex relative flex-col items-center text-gray-300 text-lg mx-auto px-6 max-w-3xl lg:max-w-4xl xl:max-w-5xl py-6">
<div className="text-5xl font-bold text-left w-full mt-12">Your quick start guide</div>
<div className="text-lg text-left w-full pt-2 pb-4 mb-14 text-bunker-300">
Click on the items below and follow the instructions.
</div>
<div className='absolute h-min top-0 right-0 hidden lg:block'><Image src="/images/dragon-book.svg" height={250} width={400} alt="start guise dragon illustration" /></div>
{learningItem({
text: 'Get to know Infisical',
subText: '',
@@ -170,17 +173,32 @@ export default function Home() {
userAction: 'first_time_secrets_pushed',
link: `/dashboard/${router.query.id}`
})}
{learningItem({
text: 'Inject secrets locally',
subText: 'Replace .env files with a more secure and efficient alternative.',
complete: false,
icon: faNetworkWired,
time: '8 min',
link: 'https://infisical.com/docs/getting-started/quickstart'
})}
<div className="relative group bg-bunker-500 shadow-xl duration-200 rounded-md border border-mineshaft-600 pl-2 pr-2 pt-4 pb-2 h-full w-full flex flex-col items-center justify-between overflow-hidden mb-3 cursor-default">
<div className='w-full flex flex-row items-center mb-4 pr-4'>
<div className="flex flex-row items-center mr-4 w-full">
<FontAwesomeIcon icon={faNetworkWired} className="text-4xl mx-2 w-16" />
{false && (
<div className="bg-bunker-500 group-hover:bg-mineshaft-700 w-7 h-7 rounded-full absolute left-12 top-10 p-2 flex items-center justify-center">
<FontAwesomeIcon icon={faCheckCircle} className="text-4xl w-5 h-5 text-green" />
</div>
)}
<div className="flex pl-0.5 flex-col items-start">
<div className="text-xl font-semibold mt-0.5">Inject secrets locally</div>
<div className="text-sm font-normal">Replace .env files with a more secure and efficient alternative.</div>
</div>
</div>
<div
className={`pr-4 font-semibold text-sm w-28 text-right ${false && 'text-green'}`}
>
About 2 min
</div>
</div>
<TabsObject/>
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
</div>
{learningItem({
text: 'Integrate Infisical with your infrastructure',
subText: 'Only a few integrations are currently available. Many more coming soon!',
subText: 'Connect Infisical to various 3rd party services and platforms.',
complete: false,
icon: faPlug,
time: '15 min',
+1 -1
View File
@@ -20,7 +20,7 @@ export default function RequestNewInvite() {
doesn&apos;t work, please reach out to us at support@infisical.com
</p>
<div className="">
<Image src="/images/invitation-expired.svg" height={500} width={800} alt="google logo" />
<Image src="/images/invitation-expired.svg" height={500} width={800} alt="invitation expired illustration" />
</div>
</div>
</div>
+2 -2
View File
@@ -31,7 +31,7 @@ export default function SettingsBilling() {
{
key: 2,
name: 'Team',
price: '$7',
price: '$8',
priceExplanation: t('billing:professional.price-explanation')!,
text: 'For teams that want to improve their efficiency and security.',
buttonTextMain: t('billing:upgrade')!,
@@ -41,7 +41,7 @@ export default function SettingsBilling() {
{
key: 3,
name: t('billing:professional.name')!,
price: '$14',
price: '$18',
priceExplanation: t('billing:professional.price-explanation')!,
text: t('billing:enterprise.text')!,
subtext: t('billing:professional.subtext')!,
-1
View File
@@ -222,7 +222,6 @@ export default function SettingsOrg() {
submitModal={submitAddUserModal}
email={emailUser}
setEmail={setEmailUser}
currentPlan={currentPlan}
orgName={orgName}
/>
<UpgradePlanModal