mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 08:27:22 +00:00
Fixing UI, UX, and bugs in the dashboard
This commit is contained in:
@@ -26,7 +26,8 @@ const buttonVariants = cva(
|
|||||||
colorSchema: {
|
colorSchema: {
|
||||||
primary: ['bg-primary', 'text-black', 'border-primary bg-opacity-80 hover:bg-opacity-100'],
|
primary: ['bg-primary', 'text-black', 'border-primary bg-opacity-80 hover:bg-opacity-100'],
|
||||||
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
|
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
|
||||||
danger: ['bg-red', 'text-white', 'border-red hover:bg-opacity-90']
|
danger: ['bg-red', 'text-white', 'border-red hover:bg-opacity-90'],
|
||||||
|
gray: ['bg-bunker-500', 'text-bunker-200']
|
||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
solid: '',
|
solid: '',
|
||||||
@@ -85,6 +86,11 @@ const buttonVariants = cva(
|
|||||||
variant: 'plain',
|
variant: 'plain',
|
||||||
className: 'text-primary'
|
className: 'text-primary'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
colorSchema: 'gray',
|
||||||
|
variant: 'plain',
|
||||||
|
className: 'bg-transparent text-bunker-200'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
colorSchema: 'secondary',
|
colorSchema: 'secondary',
|
||||||
variant: 'plain',
|
variant: 'plain',
|
||||||
@@ -165,7 +171,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
>
|
>
|
||||||
{leftIcon}
|
{leftIcon}
|
||||||
</div>
|
</div>
|
||||||
<span className={twMerge('w-full transition-all', loadingToggleClass)}>{children}</span>
|
<span className={twMerge('transition-all', isFullWidth ? 'w-full' : 'w-min', loadingToggleClass)}>{children}</span>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'inline-flex shrink-0 cursor-pointer items-center justify-center transition-all',
|
'inline-flex shrink-0 cursor-pointer items-center justify-center transition-all',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export type CardFooterProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CardFooter = ({ children, className }: CardFooterProps) => (
|
export const CardFooter = ({ children, className }: CardFooterProps) => (
|
||||||
<div className={twMerge('p-6 pt-0', className)}>{children}</div>
|
<div className={twMerge('p-4 pt-0', className)}>{children}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type CardBodyProps = {
|
export type CardBodyProps = {
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
|
|||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={twMerge(drawerContentVariation({ direction, className }))}
|
className={twMerge(drawerContentVariation({ direction, className }))}
|
||||||
>
|
>
|
||||||
<Card isRounded={false} className="h-full w-full">
|
<Card isRounded={false} className="h-full w-full dark">
|
||||||
{title && (
|
{title && (
|
||||||
<CardTitle subTitle={subTitle} className="px-4">
|
<CardTitle subTitle={subTitle} className="px-4">
|
||||||
{title}
|
{title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
)}
|
)}
|
||||||
<CardBody className="flex-grow overflow-y-auto overflow-x-hidden px-4">
|
<CardBody className="flex-grow overflow-y-auto overflow-x-hidden px-4 dark:[color-scheme:dark]">
|
||||||
{children}
|
{children}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{footerContent && <CardFooter>{footerContent}</CardFooter>}{' '}
|
{footerContent && <CardFooter>{footerContent}</CardFooter>}{' '}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const iconButtonVariants = cva(
|
|||||||
colorSchema: {
|
colorSchema: {
|
||||||
primary: ['bg-primary', 'text-black', 'border-primary hover:opacity-80'],
|
primary: ['bg-primary', 'text-black', 'border-primary hover:opacity-80'],
|
||||||
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-bunker-400'],
|
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-bunker-400'],
|
||||||
danger: ['bg-[#973939] hover:bg-red', 'text-white', 'border-red']
|
danger: ['bg-[#973939]', 'text-white', 'border-red']
|
||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
solid: '',
|
solid: '',
|
||||||
@@ -41,7 +41,7 @@ const iconButtonVariants = cva(
|
|||||||
false: ''
|
false: ''
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
xs: ['text-xs', 'py-1.5', 'px-2'],
|
xs: ['text-xs', 'rounded-sm', 'py-1.5', 'px-2'],
|
||||||
sm: ['text-sm', 'py-3', 'px-3'],
|
sm: ['text-sm', 'py-3', 'px-3'],
|
||||||
md: ['text-md', 'py-4', 'px-4'],
|
md: ['text-md', 'py-4', 'px-4'],
|
||||||
lg: ['text-lg', 'py-6', 'px-6']
|
lg: ['text-lg', 'py-6', 'px-6']
|
||||||
@@ -76,7 +76,7 @@ const iconButtonVariants = cva(
|
|||||||
{
|
{
|
||||||
colorSchema: 'primary',
|
colorSchema: 'primary',
|
||||||
variant: 'plain',
|
variant: 'plain',
|
||||||
className: 'text-primary'
|
className: 'hover:text-primary'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
colorSchema: 'secondary',
|
colorSchema: 'secondary',
|
||||||
@@ -86,7 +86,7 @@ const iconButtonVariants = cva(
|
|||||||
{
|
{
|
||||||
colorSchema: 'danger',
|
colorSchema: 'danger',
|
||||||
variant: 'plain',
|
variant: 'plain',
|
||||||
className: 'text-red'
|
className: 'hover:text-red'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
colorSchema: ['danger', 'primary', 'secondary'],
|
colorSchema: ['danger', 'primary', 'secondary'],
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const Tooltip = ({
|
|||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
`z-20 select-none rounded-md bg-mineshaft-500 py-2 px-4 text-sm shadow-md
|
`z-20 select-none rounded-md bg-mineshaft-500 py-2 px-4 text-sm text-bunker-200 shadow-md
|
||||||
data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade
|
data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade
|
||||||
data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade
|
data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade
|
||||||
data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade
|
data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const DashboardPage = () => {
|
|||||||
'uploadedSecOpts',
|
'uploadedSecOpts',
|
||||||
'compareSecrets'
|
'compareSecrets'
|
||||||
] as const);
|
] as const);
|
||||||
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle();
|
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle(true);
|
||||||
const [searchFilter, setSearchFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
const [snapshotId, setSnaphotId] = useState<string | null>(null);
|
const [snapshotId, setSnaphotId] = useState<string | null>(null);
|
||||||
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null);
|
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null);
|
||||||
@@ -178,6 +178,7 @@ export const DashboardPage = () => {
|
|||||||
|
|
||||||
const method = useForm<FormData>({
|
const method = useForm<FormData>({
|
||||||
// why any: well yup inferred ts expects other keys to defined as undefined
|
// why any: well yup inferred ts expects other keys to defined as undefined
|
||||||
|
defaultValues: secrets as any,
|
||||||
values: secrets as any,
|
values: secrets as any,
|
||||||
mode: 'onBlur',
|
mode: 'onBlur',
|
||||||
resolver: yupResolver(schema)
|
resolver: yupResolver(schema)
|
||||||
@@ -191,6 +192,8 @@ export const DashboardPage = () => {
|
|||||||
formState: { isDirty, isSubmitting, dirtyFields },
|
formState: { isDirty, isSubmitting, dirtyFields },
|
||||||
reset
|
reset
|
||||||
} = method;
|
} = method;
|
||||||
|
console.log(122, method)
|
||||||
|
console.log(123, isDirty, Object.keys(dirtyFields))
|
||||||
const formSecrets = useWatch({ control, name: 'secrets' });
|
const formSecrets = useWatch({ control, name: 'secrets' });
|
||||||
const { fields, prepend, append, remove, update } = useFieldArray({ control, name: 'secrets' });
|
const { fields, prepend, append, remove, update } = useFieldArray({ control, name: 'secrets' });
|
||||||
|
|
||||||
@@ -198,12 +201,13 @@ export const DashboardPage = () => {
|
|||||||
const isReadOnly = selectedEnv?.isWriteDenied;
|
const isReadOnly = selectedEnv?.isWriteDenied;
|
||||||
const isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
|
const isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
|
||||||
const canDoRollback = !isReadOnly && !isAddOnly;
|
const canDoRollback = !isReadOnly && !isAddOnly;
|
||||||
|
console.log(123, !isRollbackMode, !isAddOnly, !isDirty)
|
||||||
const isSubmitDisabled =
|
const isSubmitDisabled =
|
||||||
isReadOnly ||
|
isReadOnly ||
|
||||||
// on add only mode the formstate becomes dirty due to secrets missing some items
|
// on add only mode the formstate becomes dirty due to secrets missing some items
|
||||||
// to avoid this we check dirtyFields in isAddOnly Mode
|
// to avoid this we check dirtyFields in isAddOnly Mode
|
||||||
(isAddOnly && Object.keys(dirtyFields).length === 0) ||
|
(isAddOnly && Object.keys(dirtyFields).length === 0) ||
|
||||||
(!isRollbackMode && !isAddOnly && !isDirty) ||
|
(!isRollbackMode && !isAddOnly && Object.keys(dirtyFields).length === 0) ||
|
||||||
isSubmitting;
|
isSubmitting;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -411,7 +415,7 @@ export const DashboardPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto w-full px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto w-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<FormProvider {...method}>
|
<FormProvider {...method}>
|
||||||
<form autoComplete="off">
|
<form autoComplete="off">
|
||||||
{/* breadcrumb row */}
|
{/* breadcrumb row */}
|
||||||
@@ -439,6 +443,7 @@ export const DashboardPage = () => {
|
|||||||
setSnaphotId(null);
|
setSnaphotId(null);
|
||||||
reset({ ...secrets, isSnapshotMode: false });
|
reset({ ...secrets, isSnapshotMode: false });
|
||||||
}}
|
}}
|
||||||
|
className='h-10'
|
||||||
>
|
>
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
@@ -449,6 +454,7 @@ export const DashboardPage = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
|
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
|
||||||
isLoading={isLoadingSnapshotCount}
|
isLoading={isLoadingSnapshotCount}
|
||||||
isDisabled={!canDoRollback}
|
isDisabled={!canDoRollback}
|
||||||
|
className='h-10'
|
||||||
>
|
>
|
||||||
{snapshotCount} Commits
|
{snapshotCount} Commits
|
||||||
</Button>
|
</Button>
|
||||||
@@ -457,6 +463,7 @@ export const DashboardPage = () => {
|
|||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
leftIcon={<FontAwesomeIcon icon={isRollbackMode ? faClockRotateLeft : faCheck} />}
|
leftIcon={<FontAwesomeIcon icon={isRollbackMode ? faClockRotateLeft : faCheck} />}
|
||||||
onClick={handleSubmit(onSaveSecret)}
|
onClick={handleSubmit(onSaveSecret)}
|
||||||
|
className='h-10'
|
||||||
>
|
>
|
||||||
{isRollbackMode ? 'Rollback' : 'Save Changes'}
|
{isRollbackMode ? 'Rollback' : 'Save Changes'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -470,7 +477,8 @@ export const DashboardPage = () => {
|
|||||||
value={selectedEnv?.slug}
|
value={selectedEnv?.slug}
|
||||||
onValueChange={onEnvChange}
|
onValueChange={onEnvChange}
|
||||||
position="popper"
|
position="popper"
|
||||||
className="min-w-[180px] bg-mineshaft-600"
|
className="min-w-[180px] bg-mineshaft-600 h-10 font-medium"
|
||||||
|
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
|
||||||
>
|
>
|
||||||
{userAvailableEnvs?.map(({ name, slug }) => (
|
{userAvailableEnvs?.map(({ name, slug }) => (
|
||||||
<SelectItem value={slug} key={slug}>
|
<SelectItem value={slug} key={slug}>
|
||||||
@@ -482,7 +490,7 @@ export const DashboardPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<Input
|
<Input
|
||||||
className="bg-mineshaft-600 placeholder-mineshaft-50"
|
className="bg-mineshaft-600 h-[2.3rem] placeholder-mineshaft-50"
|
||||||
placeholder="Search keys..."
|
placeholder="Search keys..."
|
||||||
value={searchFilter}
|
value={searchFilter}
|
||||||
onChange={(e) => setSearchFilter(e.target.value)}
|
onChange={(e) => setSearchFilter(e.target.value)}
|
||||||
@@ -497,12 +505,12 @@ export const DashboardPage = () => {
|
|||||||
<FontAwesomeIcon icon={faDownload} />
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto">
|
<PopoverContent className="w-auto bg-mineshaft-800 border border-mineshaft-600 p-1" hideCloseBtn>
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => downloadSecret(getValues('secrets'), selectedEnv?.slug)}
|
onClick={() => downloadSecret(getValues('secrets'), selectedEnv?.slug)}
|
||||||
variant="star"
|
variant="star"
|
||||||
className="bg-bunker-800"
|
className="bg-bunker-700 h-8"
|
||||||
>
|
>
|
||||||
Download as .env
|
Download as .env
|
||||||
</Button>
|
</Button>
|
||||||
@@ -510,34 +518,37 @@ export const DashboardPage = () => {
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip content={isSecretValueHidden ? 'Hide Secrets' : 'Reveal secrets'}>
|
<div>
|
||||||
<IconButton
|
<Tooltip content={isSecretValueHidden ? 'Reveal Secrets' : 'Hide secrets'}>
|
||||||
ariaLabel="reveal"
|
<IconButton
|
||||||
variant="star"
|
ariaLabel="reveal"
|
||||||
onClick={() => setIsSecretValueHidden.toggle()}
|
variant="star"
|
||||||
>
|
onClick={() => setIsSecretValueHidden.toggle()}
|
||||||
<FontAwesomeIcon icon={isSecretValueHidden ? faEyeSlash : faEye} />
|
>
|
||||||
</IconButton>
|
<FontAwesomeIcon icon={isSecretValueHidden ? faEye : faEyeSlash} />
|
||||||
</Tooltip>
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
|
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
|
||||||
isDisabled={isReadOnly || isRollbackMode}
|
isDisabled={isReadOnly || isRollbackMode}
|
||||||
variant="star"
|
variant="star"
|
||||||
|
className="h-10"
|
||||||
>
|
>
|
||||||
Add Secret
|
Add Secret
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className={`${isSecretEmpty ? "flex flex-col items-center justify-center" : ""} mt-4 h-[calc(100vh-270px)] overflow-y-scroll overflow-x-hidden no-scrollbar no-scrollbar::-webkit-scrollbar`}>
|
||||||
{!isSecretEmpty && (
|
{!isSecretEmpty && (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<table className="secret-table">
|
<table className="secret-table relative">
|
||||||
<SecretTableHeader
|
<SecretTableHeader
|
||||||
sortDir={sortDir}
|
sortDir={sortDir}
|
||||||
onSort={onSortSecrets}
|
onSort={onSortSecrets}
|
||||||
/>
|
/>
|
||||||
<tbody>
|
<tbody className="overflow-y-auto max-h-screen">
|
||||||
{fields.map(({ id, _id }, index) => (
|
{fields.map(({ id, _id }, index) => (
|
||||||
<SecretInputRow
|
<SecretInputRow
|
||||||
key={id}
|
key={id}
|
||||||
@@ -555,15 +566,15 @@ export const DashboardPage = () => {
|
|||||||
))}
|
))}
|
||||||
{!isReadOnly && !isRollbackMode && (
|
{!isReadOnly && !isRollbackMode && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={3}>
|
<td colSpan={3} className="hover:bg-mineshaft-700">
|
||||||
<Button
|
<button
|
||||||
variant="plain"
|
type="button"
|
||||||
className="my-1"
|
className="w-[calc(100vw-400px)] h-8 ml-12 font-normal text-bunker-300 flex justify-start items-center"
|
||||||
onClick={onAppendSecret}
|
onClick={onAppendSecret}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
>
|
>
|
||||||
Add Secret
|
<FontAwesomeIcon icon={faPlus} />
|
||||||
</Button>
|
<span className="w-20 ml-2">Add Secret</span>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
@@ -650,7 +661,7 @@ export const DashboardPage = () => {
|
|||||||
>
|
>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={popUp?.compareSecrets?.data as string}
|
title={popUp?.compareSecrets?.data as string}
|
||||||
subTitle="Secret value in available environments"
|
subTitle="Below is the comparison of secret values across available environments"
|
||||||
overlayClassName="z-[90]"
|
overlayClassName="z-[90]"
|
||||||
>
|
>
|
||||||
<CompareSecret
|
<CompareSecret
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const SecretValue = ({ workspaceId, env, envName, secretKey }: SecretValueProps)
|
|||||||
return (
|
return (
|
||||||
<FormControl label={envName}>
|
<FormControl label={envName}>
|
||||||
<Input
|
<Input
|
||||||
className="w-full text-ellipsis font-mono focus:text-bunker-100 focus:ring-transparent"
|
className={`w-full text-ellipsis font-mono focus:ring-transparent ${getValue(secret) === "Not found" && "text-mineshaft-500"}`}
|
||||||
value={getValue(secret)}
|
value={getValue(secret)}
|
||||||
isReadOnly
|
isReadOnly
|
||||||
rightIcon={isSecretsLoading ? <Spinner /> : undefined}
|
rightIcon={isSecretsLoading ? <Spinner /> : undefined}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export const PitDrawer = ({
|
|||||||
<Button
|
<Button
|
||||||
key={_id}
|
key={_id}
|
||||||
className="py-3 px-4 text-sm"
|
className="py-3 px-4 text-sm"
|
||||||
|
isFullWidth
|
||||||
variant={
|
variant={
|
||||||
(i === 0 && index === 0 && snapshotId === null) || snapshotId === _id
|
(i === 0 && index === 0 && snapshotId === null) || snapshotId === _id
|
||||||
? 'solid'
|
? 'solid'
|
||||||
|
|||||||
+8
-8
@@ -74,10 +74,10 @@ export const SecretDetailDrawer = ({
|
|||||||
return (
|
return (
|
||||||
<Drawer onOpenChange={onOpenChange} isOpen={isDrawerOpen}>
|
<Drawer onOpenChange={onOpenChange} isOpen={isDrawerOpen}>
|
||||||
<DrawerContent
|
<DrawerContent
|
||||||
className="border-l border-mineshaft-500 bg-bunker"
|
className="border-l border-mineshaft-500 bg-bunker dark"
|
||||||
title="Secret"
|
title="Secret"
|
||||||
footerContent={
|
footerContent={
|
||||||
<div className="flex flex-col space-y-4 pt-4 shadow-md">
|
<div className="flex flex-col space-y-2 pt-4 shadow-md">
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
variant="star"
|
variant="star"
|
||||||
@@ -103,7 +103,7 @@ export const SecretDetailDrawer = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="">
|
<div className="dark:[color-scheme:dark]">
|
||||||
<FormControl label="Key">
|
<FormControl label="Key">
|
||||||
<Input isDisabled {...register(`secrets.${index}.key`)} />
|
<Input isDisabled {...register(`secrets.${index}.key`)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -173,11 +173,11 @@ export const SecretDetailDrawer = ({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div className="mb-4 text-sm text-bunker-300">
|
<div className="mb-4 text-sm text-bunker-300 dark">
|
||||||
<div className="mb-2">Version History</div>
|
<div className="mb-2">Version History</div>
|
||||||
<div className="flex h-52 flex-col space-y-4 overflow-y-auto overflow-x-hidden rounded-md bg-bunker-800 p-2">
|
<div className="flex h-48 flex-col space-y-2 border border-mineshaft-600 overflow-y-auto overflow-x-hidden rounded-md bg-bunker-800 p-2 dark:[color-scheme:dark]">
|
||||||
{secretVersion?.map(({ createdAt, value, id }, i) => (
|
{secretVersion?.map(({ createdAt, value, id }, i) => (
|
||||||
<div key={id} className="flex flex-col space-y-2">
|
<div key={id} className="flex flex-col space-y-1">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={i === 0 ? faCircleDot : faCircle} size="sm" />
|
<FontAwesomeIcon icon={i === 0 ? faCircleDot : faCircle} size="sm" />
|
||||||
@@ -193,7 +193,7 @@ export const SecretDetailDrawer = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 flex items-center space-x-2 border-l border-bunker-300 pl-4">
|
<div className="ml-1.5 flex items-center space-x-2 border-l border-bunker-300 pl-4">
|
||||||
<div className="rounded-sm bg-primary-500/30 px-1">Value:</div>
|
<div className="rounded-sm bg-primary-500/30 px-1">Value:</div>
|
||||||
<div className="font-mono">{value}</div>
|
<div className="font-mono">{value}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,7 +202,7 @@ export const SecretDetailDrawer = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FormControl label="Comments & Notes">
|
<FormControl label="Comments & Notes">
|
||||||
<TextArea isDisabled={isReadOnly} {...register(`secrets.${index}.comment`)} rows={5} />
|
<TextArea className="border border-mineshaft-600 text-sm" isDisabled={isReadOnly} {...register(`secrets.${index}.comment`)} rows={5} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
|
|||||||
@@ -84,15 +84,15 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
|
|||||||
onDragOver={handleDrag}
|
onDragOver={handleDrag}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative mb-4 mt-4 flex w-full cursor-pointer items-center justify-center space-x-2 rounded-md bg-mineshaft-900 py-8 px-2 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100',
|
'relative mb-4 mt-4 max-w-[calc(100vw-292px)] flex w-full cursor-pointer text-mineshaft-200 items-center py-8 justify-center space-x-2 rounded-md bg-mineshaft-900 px-2 mx-0.5 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100',
|
||||||
isDragActive && 'opacity-100',
|
isDragActive && 'opacity-100',
|
||||||
!isSmaller && 'flex-col space-y-4',
|
!isSmaller && 'flex-col space-y-4 max-w-3xl py-20',
|
||||||
isLoading && 'bg-bunker-800'
|
isLoading && 'bg-bunker-800'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="mb-16 flex items-center justify-center pt-16">
|
<div className="mb-16 flex items-center justify-center pt-16">
|
||||||
<img src="/images/loading/loading.gif" height={70} width={120} alt="google logo" />
|
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -112,9 +112,9 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
|
|||||||
{!isSmaller && (
|
{!isSmaller && (
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full flex-row items-center justify-center py-4">
|
<div className="flex w-full flex-row items-center justify-center py-4">
|
||||||
<div className="w-1/5 border-t border-gray-700" />
|
<div className="w-1/5 border-t border-mineshaft-700" />
|
||||||
<p className="mx-4 text-xs text-gray-400">OR</p>
|
<p className="mx-4 text-xs text-mineshaft-400">OR</p>
|
||||||
<div className="w-1/5 border-t border-gray-700" />
|
<div className="w-1/5 border-t border-mineshaft-700" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button variant="star" onClick={onAddNewSecret}>
|
<Button variant="star" onClick={onAddNewSecret}>
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
/* eslint-disable react/jsx-no-useless-fragment */
|
/* eslint-disable react/jsx-no-useless-fragment */
|
||||||
|
import { SyntheticEvent, useRef } from 'react';
|
||||||
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
||||||
import {
|
import {
|
||||||
|
faCircle,
|
||||||
faCodeBranch,
|
faCodeBranch,
|
||||||
faComment,
|
faComment,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faInfoCircle,
|
faInfoCircle,
|
||||||
faPlus,
|
faPlus,
|
||||||
faSquare,
|
|
||||||
faSquareCheck,
|
|
||||||
faTags,
|
faTags,
|
||||||
faTrash
|
faXmark
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { cx } from 'cva';
|
import { cx } from 'cva';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
import guidGenerator from '@app/components/utilities/randomId';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
FormControl,
|
FormControl,
|
||||||
HoverCard,
|
HoverCard,
|
||||||
HoverCardContent,
|
HoverCardContent,
|
||||||
@@ -62,6 +64,7 @@ const tagColors = [
|
|||||||
{ bg: 'bg-[#C40B13]/40', text: 'text-[#FFDEDE]/70' },
|
{ bg: 'bg-[#C40B13]/40', text: 'text-[#FFDEDE]/70' },
|
||||||
{ bg: 'bg-[#332FD0]/40', text: 'text-[#DFF6FF]/70' }
|
{ bg: 'bg-[#332FD0]/40', text: 'text-[#DFF6FF]/70' }
|
||||||
];
|
];
|
||||||
|
const REGEX = /([$]{.*?})/g;
|
||||||
|
|
||||||
export const SecretInputRow = ({
|
export const SecretInputRow = ({
|
||||||
index,
|
index,
|
||||||
@@ -75,8 +78,15 @@ export const SecretInputRow = ({
|
|||||||
onSecretDelete,
|
onSecretDelete,
|
||||||
searchTerm
|
searchTerm
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
|
||||||
|
if (ref.current === null) return;
|
||||||
|
|
||||||
|
ref.current.scrollTop = e.currentTarget.scrollTop;
|
||||||
|
ref.current.scrollLeft = e.currentTarget.scrollLeft;
|
||||||
|
};
|
||||||
const { register, setValue, control } = useFormContext<FormData>();
|
const { register, setValue, control } = useFormContext<FormData>();
|
||||||
const [canRevealSecret, setCanRevealSecret] = useToggle();
|
const [canRevealSecret] = useToggle();
|
||||||
// comment management in a row
|
// comment management in a row
|
||||||
const {
|
const {
|
||||||
fields: secretTags,
|
fields: secretTags,
|
||||||
@@ -86,7 +96,6 @@ export const SecretInputRow = ({
|
|||||||
|
|
||||||
// to get details on a secret
|
// to get details on a secret
|
||||||
const secret = useWatch({ name: `secrets.${index}`, control });
|
const secret = useWatch({ name: `secrets.${index}`, control });
|
||||||
|
|
||||||
const hasComment = Boolean(secret.comment);
|
const hasComment = Boolean(secret.comment);
|
||||||
const tags = secret.tags || [];
|
const tags = secret.tags || [];
|
||||||
const selectedTagIds = tags.reduce<Record<string, boolean>>(
|
const selectedTagIds = tags.reduce<Record<string, boolean>>(
|
||||||
@@ -106,6 +115,7 @@ export const SecretInputRow = ({
|
|||||||
setValue(`secrets.${index}.valueOverride`, '');
|
setValue(`secrets.${index}.valueOverride`, '');
|
||||||
setValue(`secrets.${index}.overrideAction`, SecretActionType.Deleted, { shouldDirty: true });
|
setValue(`secrets.${index}.overrideAction`, SecretActionType.Deleted, { shouldDirty: true });
|
||||||
} else {
|
} else {
|
||||||
|
setValue(`secrets.${index}.valueOverride`, '');
|
||||||
setValue(
|
setValue(
|
||||||
`secrets.${index}.overrideAction`,
|
`secrets.${index}.overrideAction`,
|
||||||
secret?.idOverride ? SecretActionType.Modified : SecretActionType.Created,
|
secret?.idOverride ? SecretActionType.Modified : SecretActionType.Created,
|
||||||
@@ -146,8 +156,8 @@ export const SecretInputRow = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className="group min-w-full flex flex-row">
|
<tr className="group min-w-full flex flex-row items-center">
|
||||||
<td className="w-10 px-4 flex items-center justify-center"><div className='text-center text-xs text-bunker-400'>{index + 1}</div></td>
|
<td className="w-10 h-10 px-4 flex items-center justify-center"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
@@ -156,30 +166,67 @@ export const SecretInputRow = ({
|
|||||||
<HoverCard openDelay={0} open={error?.message ? undefined : false}>
|
<HoverCard openDelay={0} open={error?.message ? undefined : false}>
|
||||||
<HoverCardTrigger asChild>
|
<HoverCardTrigger asChild>
|
||||||
<td className={cx(error?.message ? 'rounded ring ring-red/50' : null)}>
|
<td className={cx(error?.message ? 'rounded ring ring-red/50' : null)}>
|
||||||
<div className="min-w-[220px] relative flex items-center">
|
<div className="min-w-[220px] lg:min-w-[240px] xl:min-w-[280px] relative flex items-center justify-end w-full">
|
||||||
<Input
|
<Input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
isDisabled={isReadOnly || shouldBeBlockedInAddOnly || isRollbackMode}
|
isDisabled={isReadOnly || shouldBeBlockedInAddOnly || isRollbackMode}
|
||||||
className="w-full text-ellipsis font-mono focus:text-bunker-100 focus:ring-transparent"
|
className="w-full focus:text-bunker-100 focus:ring-transparent"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
{!isAddOnly && (
|
<div className="w-max flex flex-row items-center justify-end">
|
||||||
<IconButton
|
<Tooltip content="Comment">
|
||||||
variant="plain"
|
<div className={`${hasComment ? "w-5" : "w-0"} overflow-hidden group-hover:w-5 mt-0.5`}>
|
||||||
className={twMerge(
|
<Popover>
|
||||||
'w-0 overflow-hidden p-0 group-hover:w-6',
|
<PopoverTrigger asChild>
|
||||||
isOverridden && 'w-6 text-primary'
|
<IconButton
|
||||||
)}
|
className={twMerge(
|
||||||
onClick={onSecretOverride}
|
'w-0 overflow-hidden p-0 group-hover:w-5',
|
||||||
isDisabled={isRollbackMode || isReadOnly}
|
hasComment && 'w-5 text-primary'
|
||||||
ariaLabel="info"
|
)}
|
||||||
>
|
variant="plain"
|
||||||
<div className="flex items-center space-x-1">
|
size="md"
|
||||||
<FontAwesomeIcon icon={faCodeBranch} className="text-base" />
|
ariaLabel="add-tag"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faComment} />
|
||||||
|
</IconButton>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl p-2">
|
||||||
|
<FormControl label="Comment" className="mb-0">
|
||||||
|
<TextArea
|
||||||
|
isDisabled={isReadOnly || isRollbackMode || shouldBeBlockedInAddOnly}
|
||||||
|
className="border border-mineshaft-600 text-sm"
|
||||||
|
{...register(`secrets.${index}.comment`)}
|
||||||
|
rows={8}
|
||||||
|
cols={30}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</IconButton>
|
</Tooltip>
|
||||||
)}
|
{!isAddOnly && (
|
||||||
|
<div>
|
||||||
|
<Tooltip content="Override with a personal value">
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={twMerge(
|
||||||
|
'w-0 overflow-hidden p-0 group-hover:w-6 group-hover:ml-1 mt-0.5',
|
||||||
|
isOverridden && 'w-6 text-primary ml-1'
|
||||||
|
)}
|
||||||
|
onClick={onSecretOverride}
|
||||||
|
size="md"
|
||||||
|
isDisabled={isRollbackMode || isReadOnly}
|
||||||
|
ariaLabel="info"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<FontAwesomeIcon icon={faCodeBranch} className="text-base" />
|
||||||
|
</div>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
@@ -194,36 +241,86 @@ export const SecretInputRow = ({
|
|||||||
</HoverCard>
|
</HoverCard>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex-grow w-full">
|
<td className="flex flex-row w-full justify-center h-8 items-center">
|
||||||
{!isOverridden && (
|
<div className="group relative whitespace-pre flex flex-col justify-center w-full px-1.5">
|
||||||
<Input
|
{isOverridden
|
||||||
variant="plain"
|
? <input
|
||||||
isReadOnly={isReadOnly || shouldBeBlockedInAddOnly || isRollbackMode}
|
|
||||||
className="w-full text-ellipsis font-mono focus:text-bunker-100 focus:ring-transparent"
|
|
||||||
type={canRevealSecret || isSecretValueHidden ? 'text' : 'password'}
|
|
||||||
{...register(`secrets.${index}.value`)}
|
|
||||||
placeholder="EMPTY"
|
|
||||||
onBlur={setCanRevealSecret.off}
|
|
||||||
onFocus={setCanRevealSecret.on}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isOverridden && (
|
|
||||||
<Input
|
|
||||||
variant="plain"
|
|
||||||
isReadOnly={isReadOnly || isAddOnly || isRollbackMode}
|
|
||||||
className="w-full text-ellipsis font-mono focus:text-bunker-100 focus:ring-transparent"
|
|
||||||
type={canRevealSecret || isSecretValueHidden ? 'text' : 'password'}
|
|
||||||
placeholder="EMPTY"
|
|
||||||
{...register(`secrets.${index}.valueOverride`)}
|
{...register(`secrets.${index}.valueOverride`)}
|
||||||
onBlur={setCanRevealSecret.off}
|
onScroll={syncScroll}
|
||||||
onFocus={setCanRevealSecret.on}
|
readOnly={isReadOnly || isRollbackMode || (isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)}
|
||||||
autoComplete="off"
|
className={`${
|
||||||
|
(!canRevealSecret && isSecretValueHidden)
|
||||||
|
? 'text-transparent focus:text-transparent active:text-transparent'
|
||||||
|
: ''
|
||||||
|
} z-10 peer font-mono ph-no-capture bg-transparent caret-white text-transparent text-sm px-2 py-2 w-full min-w-16 outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||||
|
spellCheck="false"
|
||||||
/>
|
/>
|
||||||
)}
|
: <input
|
||||||
</div>
|
{...register(`secrets.${index}.value`)}
|
||||||
<td className="flex items-center min-w-sm">
|
onScroll={syncScroll}
|
||||||
<div className="flex items-center group-hover:mr-0 group-hover:border-r group-hover:border-mineshaft-600">
|
readOnly={isReadOnly || isRollbackMode || (isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)}
|
||||||
|
className={`${
|
||||||
|
(!canRevealSecret && isSecretValueHidden)
|
||||||
|
? 'text-transparent focus:text-transparent active:text-transparent'
|
||||||
|
: ''
|
||||||
|
} z-10 peer font-mono ph-no-capture bg-transparent caret-white text-transparent text-sm px-2 py-2 w-full min-w-16 outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||||
|
spellCheck="false"
|
||||||
|
/>}
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`${
|
||||||
|
(!canRevealSecret && isSecretValueHidden) && !isOverridden
|
||||||
|
? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200'
|
||||||
|
: ''
|
||||||
|
} ${isOverridden ? 'text-primary-300' : 'text-gray-400'}
|
||||||
|
absolute flex flex-row whitespace-pre font-mono z-0 ${(!canRevealSecret && isSecretValueHidden) ? '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`}
|
||||||
|
>
|
||||||
|
{(isOverridden ? secret.valueOverride : secret.value)?.split('').length === 0 && <span className='text-bunker-400/80 font-sans'>EMPTY</span>}
|
||||||
|
{(isOverridden ? secret.valueOverride : secret.value)?.split(REGEX).map((word) => {
|
||||||
|
if (word.match(REGEX) !== null) {
|
||||||
|
return (
|
||||||
|
<span className="ph-no-capture text-yellow" key={index}>
|
||||||
|
{word.slice(0, 2)}
|
||||||
|
<span className="ph-no-capture text-yellow-200/80">
|
||||||
|
{word.slice(2, word.length - 1)}
|
||||||
|
</span>
|
||||||
|
{word.slice(word.length - 1, word.length) === '}' ? (
|
||||||
|
<span className="ph-no-capture text-yellow">
|
||||||
|
{word.slice(word.length - 1, word.length)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="ph-no-capture text-yellow-400">
|
||||||
|
{word.slice(word.length - 1, word.length)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span key={`${word}_${index + 1}`} className="ph-no-capture">
|
||||||
|
{word}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{(!canRevealSecret && isSecretValueHidden) && (
|
||||||
|
<div className='absolute flex flex-row justify-between items-center z-0 peer pr-2 peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip'>
|
||||||
|
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||||
|
{(isOverridden ? secret.valueOverride : secret.value)?.split('').map(() => (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
key={guidGenerator()}
|
||||||
|
className="text-xxs mr-0.5"
|
||||||
|
icon={faCircle}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{(isOverridden ? secret.valueOverride : secret.value)?.split('').length === 0 && <span className='text-bunker-400/80 text-sm'>EMPTY</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="flex items-center min-w-sm h-10">
|
||||||
|
<div className="flex items-center pl-2">
|
||||||
{secretTags.map(({ id, slug }, i) => (
|
{secretTags.map(({ id, slug }, i) => (
|
||||||
<Tag
|
<Tag
|
||||||
className={cx(
|
className={cx(
|
||||||
@@ -242,8 +339,8 @@ export const SecretInputRow = ({
|
|||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div>
|
<div>
|
||||||
<Tooltip content="Add tag">
|
<Tooltip content="Add tags">
|
||||||
<IconButton variant="star" size="xs" ariaLabel="add-tag">
|
<IconButton variant="star" size="xs" ariaLabel="add-tag" className="py-[0.42rem]">
|
||||||
<FontAwesomeIcon icon={faTags} />
|
<FontAwesomeIcon icon={faTags} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -251,25 +348,29 @@ export const SecretInputRow = ({
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
side="left"
|
side="left"
|
||||||
className="max-h-96 w-auto min-w-[240px] overflow-y-auto overflow-x-hidden p-2 text-bunker-100"
|
className="max-h-96 w-auto min-w-[200px] overflow-y-auto overflow-x-hidden p-2 text-bunker-200 bg-mineshaft-800 border border-mineshaft-600"
|
||||||
hideCloseBtn
|
hideCloseBtn
|
||||||
>
|
>
|
||||||
<div className="mb-2 text-sm font-medium">Add tags to this secret</div>
|
<div className="mb-2 text-sm font-medium text-center text-bunker-200 px-2">Add tags to {secret.key || "this secret"}</div>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
{wsTags?.map((wsTag) => (
|
{wsTags?.map((wsTag) => (
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="sm"
|
size="sm"
|
||||||
isFullWidth
|
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'justify-start bg-mineshaft-800 text-bunker-100',
|
'justify-start bg-mineshaft-600 text-bunker-100 hover:bg-mineshaft-500',
|
||||||
selectedTagIds?.[wsTag.slug] && 'text-primary'
|
selectedTagIds?.[wsTag.slug] && 'text-primary'
|
||||||
)}
|
)}
|
||||||
onClick={() => onSelectTag(wsTag)}
|
onClick={() => onSelectTag(wsTag)}
|
||||||
leftIcon={
|
leftIcon={
|
||||||
<FontAwesomeIcon
|
<Checkbox
|
||||||
icon={selectedTagIds?.[wsTag.slug] ? faSquareCheck : faSquare}
|
className="data-[state=checked]:bg-primary mr-0"
|
||||||
/>
|
id="autoCapitalization"
|
||||||
|
isChecked={selectedTagIds?.[wsTag.slug]}
|
||||||
|
onCheckedChange={() => {}}
|
||||||
|
>
|
||||||
|
{}
|
||||||
|
</Checkbox>
|
||||||
}
|
}
|
||||||
key={wsTag._id}
|
key={wsTag._id}
|
||||||
>
|
>
|
||||||
@@ -277,10 +378,10 @@ export const SecretInputRow = ({
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="star"
|
||||||
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
isFullWidth
|
className="mt-4 justify-start bg-mineshaft-600 h-7 px-1"
|
||||||
className="mt-4 justify-start bg-mineshaft-800 text-bunker-400 hover:text-primary"
|
|
||||||
onClick={onCreateTagOpen}
|
onClick={onCreateTagOpen}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
>
|
>
|
||||||
@@ -292,44 +393,30 @@ export const SecretInputRow = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={`${hasComment ? "w-8" : "w-0"} overflow-hidden group-hover:w-8`}>
|
<div className="flex w-0 group-hover:w-14 invisible group-hover:visible duration-0 items-center justify-end space-x-2 overflow-hidden transition-all">
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<IconButton variant="star" size="xs" ariaLabel="add-tag">
|
|
||||||
<FontAwesomeIcon icon={faComment} />
|
|
||||||
</IconButton>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto">
|
|
||||||
<FormControl label="Comment" className="mb-0">
|
|
||||||
<TextArea
|
|
||||||
isDisabled={isReadOnly || isRollbackMode || shouldBeBlockedInAddOnly}
|
|
||||||
{...register(`secrets.${index}.comment`)}
|
|
||||||
rows={8}
|
|
||||||
cols={30}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-0 items-center justify-end space-x-2 overflow-hidden transition-all group-hover:w-16">
|
|
||||||
<Tooltip content="delete">
|
|
||||||
<IconButton
|
|
||||||
size="xs"
|
|
||||||
colorSchema="danger"
|
|
||||||
ariaLabel="delete"
|
|
||||||
isDisabled={isReadOnly || isRollbackMode}
|
|
||||||
onClick={() => onSecretDelete(index, secret._id, secret?.idOverride)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
{!isAddOnly && (
|
{!isAddOnly && (
|
||||||
<Tooltip content="more">
|
<div>
|
||||||
<IconButton size="xs" variant="solid" onClick={onRowExpand} ariaLabel="expand">
|
<Tooltip content="Settings">
|
||||||
<FontAwesomeIcon icon={faEllipsis} />
|
<IconButton size="lg" colorSchema="primary" variant="plain" onClick={onRowExpand} ariaLabel="expand">
|
||||||
|
<FontAwesomeIcon icon={faEllipsis} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<Tooltip content="Delete">
|
||||||
|
<IconButton
|
||||||
|
size="md"
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="danger"
|
||||||
|
ariaLabel="delete"
|
||||||
|
isDisabled={isReadOnly || isRollbackMode}
|
||||||
|
onClick={() => onSecretDelete(index, secret._id, secret?.idOverride)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
+56
-12
@@ -1,5 +1,8 @@
|
|||||||
import { faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons';
|
import { Controller } from 'react-hook-form';
|
||||||
|
import { faArrowDown, faArrowUp, faCodeBranch, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@radix-ui/react-hover-card';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
import { IconButton } from '@app/components/v2';
|
import { IconButton } from '@app/components/v2';
|
||||||
|
|
||||||
@@ -13,17 +16,58 @@ export const SecretTableHeader = ({
|
|||||||
onSort
|
onSort
|
||||||
}: Props): JSX.Element => (
|
}: Props): JSX.Element => (
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="flex flex-row">
|
<tr className="absolute flex flex-row sticky top-0">
|
||||||
{/* <th className="w-10 text-center"/> */}
|
<td className="w-10 px-4 flex items-center justify-center">
|
||||||
<th className='w-1/5 min-w-[220px]'>
|
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
|
||||||
<div className="inline-flex items-end">
|
</td>
|
||||||
Key
|
<Controller
|
||||||
<IconButton variant="plain" className="ml-2" ariaLabel="sort" onClick={onSort}>
|
defaultValue=""
|
||||||
<FontAwesomeIcon icon={sortDir === 'asc' ? faArrowDown : faArrowUp} />
|
name="na"
|
||||||
</IconButton>
|
render={({ fieldState: { error } }) => (
|
||||||
</div>
|
<HoverCard openDelay={0} open={error?.message ? undefined : false}>
|
||||||
</th>
|
<HoverCardTrigger asChild>
|
||||||
<th className='w-full'>Value</th>
|
<td className='flex items-center'>
|
||||||
|
<div className="min-w-[220px] lg:min-w-[240px] xl:min-w-[280px] relative flex items-center justify-start pl-2.5 w-full">
|
||||||
|
<div className="inline-flex items-end text-md font-medium">
|
||||||
|
Key
|
||||||
|
<IconButton variant="plain" className="ml-2" ariaLabel="sort" onClick={onSort}>
|
||||||
|
<FontAwesomeIcon icon={sortDir === 'asc' ? faArrowDown : faArrowUp} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div className="w-max flex flex-row items-center justify-end">
|
||||||
|
<div className="w-5 overflow-hidden group-hover:w-5 mt-1"/>
|
||||||
|
{!true && (
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={twMerge(
|
||||||
|
'w-0 overflow-hidden p-0 group-hover:w-6 group-hover:ml-1',
|
||||||
|
true && 'w-6 text-primary ml-1'
|
||||||
|
)}
|
||||||
|
size="md"
|
||||||
|
ariaLabel="info"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<FontAwesomeIcon icon={faCodeBranch} className="text-base" />
|
||||||
|
</div>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
<HoverCardContent className="w-auto py-2 pt-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} className="text-red" />
|
||||||
|
</div>
|
||||||
|
<div className="text-sm">{error?.message}</div>
|
||||||
|
</div>
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<th className="flex flex-row w-full"><div className="text-sm font-medium">Value</div></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr className='h-0 w-full border border-mineshaft-600'/>
|
||||||
</thead>
|
</thead>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user