Fixing UI, UX, and bugs in the dashboard

This commit is contained in:
Vladyslav Matsiiako
2023-04-08 17:30:28 -07:00
parent c40546945f
commit ecb182ad03
12 changed files with 312 additions and 163 deletions

View File

@ -26,7 +26,8 @@ const buttonVariants = cva(
colorSchema: {
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'],
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: {
solid: '',
@ -85,6 +86,11 @@ const buttonVariants = cva(
variant: 'plain',
className: 'text-primary'
},
{
colorSchema: 'gray',
variant: 'plain',
className: 'bg-transparent text-bunker-200'
},
{
colorSchema: 'secondary',
variant: 'plain',
@ -165,7 +171,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
>
{leftIcon}
</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
className={twMerge(
'inline-flex shrink-0 cursor-pointer items-center justify-center transition-all',

View File

@ -25,7 +25,7 @@ export type 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 = {

View File

@ -45,13 +45,13 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
ref={forwardedRef}
className={twMerge(drawerContentVariation({ direction, className }))}
>
<Card isRounded={false} className="h-full w-full">
<Card isRounded={false} className="h-full w-full dark">
{title && (
<CardTitle subTitle={subTitle} className="px-4">
{title}
</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}
</CardBody>
{footerContent && <CardFooter>{footerContent}</CardFooter>}{' '}

View File

@ -24,7 +24,7 @@ const iconButtonVariants = cva(
colorSchema: {
primary: ['bg-primary', 'text-black', 'border-primary hover:opacity-80'],
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: {
solid: '',
@ -41,7 +41,7 @@ const iconButtonVariants = cva(
false: ''
},
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'],
md: ['text-md', 'py-4', 'px-4'],
lg: ['text-lg', 'py-6', 'px-6']
@ -76,7 +76,7 @@ const iconButtonVariants = cva(
{
colorSchema: 'primary',
variant: 'plain',
className: 'text-primary'
className: 'hover:text-primary'
},
{
colorSchema: 'secondary',
@ -86,7 +86,7 @@ const iconButtonVariants = cva(
{
colorSchema: 'danger',
variant: 'plain',
className: 'text-red'
className: 'hover:text-red'
},
{
colorSchema: ['danger', 'primary', 'secondary'],

View File

@ -35,7 +35,7 @@ export const Tooltip = ({
sideOffset={5}
{...props}
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=right]:animate-slideLeftAndFade
data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade

View File

@ -99,7 +99,7 @@ export const DashboardPage = () => {
'uploadedSecOpts',
'compareSecrets'
] as const);
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle();
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle(true);
const [searchFilter, setSearchFilter] = useState('');
const [snapshotId, setSnaphotId] = useState<string | null>(null);
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null);
@ -178,6 +178,7 @@ export const DashboardPage = () => {
const method = useForm<FormData>({
// why any: well yup inferred ts expects other keys to defined as undefined
defaultValues: secrets as any,
values: secrets as any,
mode: 'onBlur',
resolver: yupResolver(schema)
@ -191,6 +192,8 @@ export const DashboardPage = () => {
formState: { isDirty, isSubmitting, dirtyFields },
reset
} = method;
console.log(122, method)
console.log(123, isDirty, Object.keys(dirtyFields))
const formSecrets = useWatch({ 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 isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
const canDoRollback = !isReadOnly && !isAddOnly;
console.log(123, !isRollbackMode, !isAddOnly, !isDirty)
const isSubmitDisabled =
isReadOnly ||
// on add only mode the formstate becomes dirty due to secrets missing some items
// to avoid this we check dirtyFields in isAddOnly Mode
(isAddOnly && Object.keys(dirtyFields).length === 0) ||
(!isRollbackMode && !isAddOnly && !isDirty) ||
(!isRollbackMode && !isAddOnly && Object.keys(dirtyFields).length === 0) ||
isSubmitting;
useEffect(() => {
@ -411,7 +415,7 @@ export const DashboardPage = () => {
);
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}>
<form autoComplete="off">
{/* breadcrumb row */}
@ -439,6 +443,7 @@ export const DashboardPage = () => {
setSnaphotId(null);
reset({ ...secrets, isSnapshotMode: false });
}}
className='h-10'
>
Go back
</Button>
@ -449,6 +454,7 @@ export const DashboardPage = () => {
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
isLoading={isLoadingSnapshotCount}
isDisabled={!canDoRollback}
className='h-10'
>
{snapshotCount} Commits
</Button>
@ -457,6 +463,7 @@ export const DashboardPage = () => {
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={isRollbackMode ? faClockRotateLeft : faCheck} />}
onClick={handleSubmit(onSaveSecret)}
className='h-10'
>
{isRollbackMode ? 'Rollback' : 'Save Changes'}
</Button>
@ -470,7 +477,8 @@ export const DashboardPage = () => {
value={selectedEnv?.slug}
onValueChange={onEnvChange}
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 }) => (
<SelectItem value={slug} key={slug}>
@ -482,7 +490,7 @@ export const DashboardPage = () => {
</div>
<div className="flex-grow">
<Input
className="bg-mineshaft-600 placeholder-mineshaft-50"
className="bg-mineshaft-600 h-[2.3rem] placeholder-mineshaft-50"
placeholder="Search keys..."
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
@ -497,12 +505,12 @@ export const DashboardPage = () => {
<FontAwesomeIcon icon={faDownload} />
</IconButton>
</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">
<Button
onClick={() => downloadSecret(getValues('secrets'), selectedEnv?.slug)}
variant="star"
className="bg-bunker-800"
className="bg-bunker-700 h-8"
>
Download as .env
</Button>
@ -510,34 +518,37 @@ export const DashboardPage = () => {
</PopoverContent>
</Popover>
</div>
<Tooltip content={isSecretValueHidden ? 'Hide Secrets' : 'Reveal secrets'}>
<IconButton
ariaLabel="reveal"
variant="star"
onClick={() => setIsSecretValueHidden.toggle()}
>
<FontAwesomeIcon icon={isSecretValueHidden ? faEyeSlash : faEye} />
</IconButton>
</Tooltip>
<div>
<Tooltip content={isSecretValueHidden ? 'Reveal Secrets' : 'Hide secrets'}>
<IconButton
ariaLabel="reveal"
variant="star"
onClick={() => setIsSecretValueHidden.toggle()}
>
<FontAwesomeIcon icon={isSecretValueHidden ? faEye : faEyeSlash} />
</IconButton>
</Tooltip>
</div>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
isDisabled={isReadOnly || isRollbackMode}
variant="star"
className="h-10"
>
Add Secret
</Button>
</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 && (
<TableContainer>
<table className="secret-table">
<table className="secret-table relative">
<SecretTableHeader
sortDir={sortDir}
onSort={onSortSecrets}
/>
<tbody>
<tbody className="overflow-y-auto max-h-screen">
{fields.map(({ id, _id }, index) => (
<SecretInputRow
key={id}
@ -555,15 +566,15 @@ export const DashboardPage = () => {
))}
{!isReadOnly && !isRollbackMode && (
<tr>
<td colSpan={3}>
<Button
variant="plain"
className="my-1"
<td colSpan={3} className="hover:bg-mineshaft-700">
<button
type="button"
className="w-[calc(100vw-400px)] h-8 ml-12 font-normal text-bunker-300 flex justify-start items-center"
onClick={onAppendSecret}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add Secret
</Button>
<FontAwesomeIcon icon={faPlus} />
<span className="w-20 ml-2">Add Secret</span>
</button>
</td>
</tr>
)}
@ -650,7 +661,7 @@ export const DashboardPage = () => {
>
<ModalContent
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]"
>
<CompareSecret

View File

@ -29,7 +29,7 @@ const SecretValue = ({ workspaceId, env, envName, secretKey }: SecretValueProps)
return (
<FormControl label={envName}>
<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)}
isReadOnly
rightIcon={isSecretsLoading ? <Spinner /> : undefined}

View File

@ -45,6 +45,7 @@ export const PitDrawer = ({
<Button
key={_id}
className="py-3 px-4 text-sm"
isFullWidth
variant={
(i === 0 && index === 0 && snapshotId === null) || snapshotId === _id
? 'solid'

View File

@ -74,10 +74,10 @@ export const SecretDetailDrawer = ({
return (
<Drawer onOpenChange={onOpenChange} isOpen={isDrawerOpen}>
<DrawerContent
className="border-l border-mineshaft-500 bg-bunker"
className="border-l border-mineshaft-500 bg-bunker dark"
title="Secret"
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>
<Button
variant="star"
@ -103,7 +103,7 @@ export const SecretDetailDrawer = ({
</div>
}
>
<div className="">
<div className="dark:[color-scheme:dark]">
<FormControl label="Key">
<Input isDisabled {...register(`secrets.${index}.key`)} />
</FormControl>
@ -173,11 +173,11 @@ export const SecretDetailDrawer = ({
</PopoverContent>
</Popover>
</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="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) => (
<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>
<FontAwesomeIcon icon={i === 0 ? faCircleDot : faCircle} size="sm" />
@ -193,7 +193,7 @@ export const SecretDetailDrawer = ({
})}
</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="font-mono">{value}</div>
</div>
@ -202,7 +202,7 @@ export const SecretDetailDrawer = ({
</div>
</div>
<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>
</div>
</DrawerContent>

View File

@ -84,15 +84,15 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
onDragOver={handleDrag}
onDrop={handleDrop}
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',
!isSmaller && 'flex-col space-y-4',
!isSmaller && 'flex-col space-y-4 max-w-3xl py-20',
isLoading && 'bg-bunker-800'
)}
>
{isLoading ? (
<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>
) : (
<>
@ -112,9 +112,9 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
{!isSmaller && (
<>
<div className="flex w-full flex-row items-center justify-center py-4">
<div className="w-1/5 border-t border-gray-700" />
<p className="mx-4 text-xs text-gray-400">OR</p>
<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-mineshaft-400">OR</p>
<div className="w-1/5 border-t border-mineshaft-700" />
</div>
<div>
<Button variant="star" onClick={onAddNewSecret}>

View File

@ -1,22 +1,24 @@
/* eslint-disable react/jsx-no-useless-fragment */
import { SyntheticEvent, useRef } from 'react';
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
import {
faCircle,
faCodeBranch,
faComment,
faEllipsis,
faInfoCircle,
faPlus,
faSquare,
faSquareCheck,
faTags,
faTrash
faXmark
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { cx } from 'cva';
import { twMerge } from 'tailwind-merge';
import guidGenerator from '@app/components/utilities/randomId';
import {
Button,
Checkbox,
FormControl,
HoverCard,
HoverCardContent,
@ -62,6 +64,7 @@ const tagColors = [
{ bg: 'bg-[#C40B13]/40', text: 'text-[#FFDEDE]/70' },
{ bg: 'bg-[#332FD0]/40', text: 'text-[#DFF6FF]/70' }
];
const REGEX = /([$]{.*?})/g;
export const SecretInputRow = ({
index,
@ -75,8 +78,15 @@ export const SecretInputRow = ({
onSecretDelete,
searchTerm
}: 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 [canRevealSecret, setCanRevealSecret] = useToggle();
const [canRevealSecret] = useToggle();
// comment management in a row
const {
fields: secretTags,
@ -86,7 +96,6 @@ export const SecretInputRow = ({
// to get details on a secret
const secret = useWatch({ name: `secrets.${index}`, control });
const hasComment = Boolean(secret.comment);
const tags = secret.tags || [];
const selectedTagIds = tags.reduce<Record<string, boolean>>(
@ -106,6 +115,7 @@ export const SecretInputRow = ({
setValue(`secrets.${index}.valueOverride`, '');
setValue(`secrets.${index}.overrideAction`, SecretActionType.Deleted, { shouldDirty: true });
} else {
setValue(`secrets.${index}.valueOverride`, '');
setValue(
`secrets.${index}.overrideAction`,
secret?.idOverride ? SecretActionType.Modified : SecretActionType.Created,
@ -146,8 +156,8 @@ export const SecretInputRow = ({
}
return (
<tr className="group min-w-full flex flex-row">
<td className="w-10 px-4 flex items-center justify-center"><div className='text-center text-xs text-bunker-400'>{index + 1}</div></td>
<tr className="group min-w-full flex flex-row items-center">
<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
control={control}
defaultValue=""
@ -156,30 +166,67 @@ export const SecretInputRow = ({
<HoverCard openDelay={0} open={error?.message ? undefined : false}>
<HoverCardTrigger asChild>
<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
autoComplete="off"
variant="plain"
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}
/>
{!isAddOnly && (
<IconButton
variant="plain"
className={twMerge(
'w-0 overflow-hidden p-0 group-hover:w-6',
isOverridden && 'w-6 text-primary'
)}
onClick={onSecretOverride}
isDisabled={isRollbackMode || isReadOnly}
ariaLabel="info"
>
<div className="flex items-center space-x-1">
<FontAwesomeIcon icon={faCodeBranch} className="text-base" />
<div className="w-max flex flex-row items-center justify-end">
<Tooltip content="Comment">
<div className={`${hasComment ? "w-5" : "w-0"} overflow-hidden group-hover:w-5 mt-0.5`}>
<Popover>
<PopoverTrigger asChild>
<IconButton
className={twMerge(
'w-0 overflow-hidden p-0 group-hover:w-5',
hasComment && 'w-5 text-primary'
)}
variant="plain"
size="md"
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>
</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>
</td>
</HoverCardTrigger>
@ -194,36 +241,86 @@ export const SecretInputRow = ({
</HoverCard>
)}
/>
<div className="flex-grow w-full">
{!isOverridden && (
<Input
variant="plain"
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"
<td className="flex flex-row w-full justify-center h-8 items-center">
<div className="group relative whitespace-pre flex flex-col justify-center w-full px-1.5">
{isOverridden
? <input
{...register(`secrets.${index}.valueOverride`)}
onBlur={setCanRevealSecret.off}
onFocus={setCanRevealSecret.on}
autoComplete="off"
onScroll={syncScroll}
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>
<td className="flex items-center min-w-sm">
<div className="flex items-center group-hover:mr-0 group-hover:border-r group-hover:border-mineshaft-600">
: <input
{...register(`secrets.${index}.value`)}
onScroll={syncScroll}
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) => (
<Tag
className={cx(
@ -242,8 +339,8 @@ export const SecretInputRow = ({
<Popover>
<PopoverTrigger asChild>
<div>
<Tooltip content="Add tag">
<IconButton variant="star" size="xs" ariaLabel="add-tag">
<Tooltip content="Add tags">
<IconButton variant="star" size="xs" ariaLabel="add-tag" className="py-[0.42rem]">
<FontAwesomeIcon icon={faTags} />
</IconButton>
</Tooltip>
@ -251,25 +348,29 @@ export const SecretInputRow = ({
</PopoverTrigger>
<PopoverContent
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
>
<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">
{wsTags?.map((wsTag) => (
<Button
variant="plain"
size="sm"
isFullWidth
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'
)}
onClick={() => onSelectTag(wsTag)}
leftIcon={
<FontAwesomeIcon
icon={selectedTagIds?.[wsTag.slug] ? faSquareCheck : faSquare}
/>
<Checkbox
className="data-[state=checked]:bg-primary mr-0"
id="autoCapitalization"
isChecked={selectedTagIds?.[wsTag.slug]}
onCheckedChange={() => {}}
>
{}
</Checkbox>
}
key={wsTag._id}
>
@ -277,10 +378,10 @@ export const SecretInputRow = ({
</Button>
))}
<Button
variant="plain"
variant="star"
color="primary"
size="sm"
isFullWidth
className="mt-4 justify-start bg-mineshaft-800 text-bunker-400 hover:text-primary"
className="mt-4 justify-start bg-mineshaft-600 h-7 px-1"
onClick={onCreateTagOpen}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
@ -292,44 +393,30 @@ export const SecretInputRow = ({
</div>
)}
</div>
<div className={`${hasComment ? "w-8" : "w-0"} overflow-hidden group-hover:w-8`}>
<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>
<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">
{!isAddOnly && (
<Tooltip content="more">
<IconButton size="xs" variant="solid" onClick={onRowExpand} ariaLabel="expand">
<FontAwesomeIcon icon={faEllipsis} />
<div>
<Tooltip content="Settings">
<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>
</Tooltip>
)}
</div>
</div>
</td>
</tr>

View File

@ -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 { HoverCard, HoverCardContent, HoverCardTrigger } from '@radix-ui/react-hover-card';
import { twMerge } from 'tailwind-merge';
import { IconButton } from '@app/components/v2';
@ -13,17 +16,58 @@ export const SecretTableHeader = ({
onSort
}: Props): JSX.Element => (
<thead>
<tr className="flex flex-row">
{/* <th className="w-10 text-center"/> */}
<th className='w-1/5 min-w-[220px]'>
<div className="inline-flex items-end">
Key
<IconButton variant="plain" className="ml-2" ariaLabel="sort" onClick={onSort}>
<FontAwesomeIcon icon={sortDir === 'asc' ? faArrowDown : faArrowUp} />
</IconButton>
</div>
</th>
<th className='w-full'>Value</th>
<tr className="absolute flex flex-row sticky top-0">
<td className="w-10 px-4 flex items-center justify-center">
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
</td>
<Controller
defaultValue=""
name="na"
render={({ fieldState: { error } }) => (
<HoverCard openDelay={0} open={error?.message ? undefined : false}>
<HoverCardTrigger asChild>
<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 className='h-0 w-full border border-mineshaft-600'/>
</thead>
);