Dashboard UI update

This commit is contained in:
Vladyslav Matsiiako
2023-05-07 12:24:40 -07:00
parent db07a033e1
commit d52534b185
10 changed files with 234 additions and 205 deletions

View File

@@ -43,7 +43,7 @@ export default function NavHeader({
const router = useRouter();
return (
<div className="ml-6 flex flex-row items-center pt-8">
<div className="ml-6 flex flex-row items-center pt-6">
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{currentOrg?.name?.charAt(0)}
</div>

View File

@@ -36,11 +36,11 @@ const buttonVariants = cva(
selected: '',
outline_bg: '',
// a constant color not in use on hover or click goes colorSchema color
star: 'text-bunker-200 bg-mineshaft-500'
star: 'text-bunker-200 bg-mineshaft-700 border-mineshaft-600'
},
isDisabled: {
true: 'bg-mineshaft text-white opacity-50 cursor-not-allowed',
false: ''
true: 'bg-mineshaft-700 border border-mineshaft-600 text-white opacity-50 cursor-not-allowed',
false: 'border border-primary-400'
},
isFullWidth: {
true: 'w-full',
@@ -61,7 +61,7 @@ const buttonVariants = cva(
{
colorSchema: 'primary',
variant: 'star',
className: 'hover:bg-primary hover:text-black'
className: 'bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary hover:text-black hover:border-primary-400 duration-100'
},
{
colorSchema: 'primary',
@@ -71,7 +71,7 @@ const buttonVariants = cva(
{
colorSchema: 'primary',
variant: 'outline_bg',
className: 'bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200'
className: 'bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200 duration-100'
},
{
colorSchema: 'secondary',

View File

@@ -30,7 +30,8 @@ const iconButtonVariants = cva(
solid: '',
outline: ['bg-transparent', 'border-2', 'border-solid'],
plain: '',
star: 'text-bunker-200 bg-mineshaft-500'
star: 'text-bunker-200 bg-mineshaft-500',
outline_bg: ''
},
isDisabled: {
true: 'bg-opacity-70 cursor-not-allowed',
@@ -53,6 +54,11 @@ const iconButtonVariants = cva(
variant: 'star',
className: 'hover:bg-primary hover:text-black'
},
{
colorSchema: 'primary',
variant: 'outline_bg',
className: 'bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200 hover:text-bunker-100 duration-100'
},
{
colorSchema: 'danger',
variant: 'star',

View File

@@ -33,7 +33,7 @@ const inputVariants = cva(
},
isError: {
true: 'focus:ring-red/50 placeholder-red-300',
false: 'focus:ring-primary/50'
false: 'focus:ring-mineshaft-400/80 duration-200 focus:ring-1'
}
},
compoundVariants: []
@@ -48,7 +48,7 @@ const inputParentContainerVariants = cva('inline-flex font-inter items-center bo
},
isError: {
true: 'border-red',
false: 'border-mineshaft-500'
false: 'border-mineshaft-600'
},
isFullWidth: {
true: 'w-full',

View File

@@ -29,11 +29,13 @@ const Dashboard = () => {
<meta property="og:title" content={String(t('dashboard:og-title'))} />
<meta name="og:description" content={String(t('dashboard:og-description'))} />
</Head>
{isOverviewMode ? (
<DashboardEnvOverview onEnvChange={onExploreEnv} />
) : (
<DashboardPage envFromTop={queryEnv} />
)}
<div className="h-full">
{isOverviewMode ? (
<DashboardEnvOverview onEnvChange={onExploreEnv} />
) : (
<DashboardPage envFromTop={queryEnv} />
)}
</div>
</>
);
};

View File

@@ -95,10 +95,10 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
<FormProvider {...method}>
<form autoComplete="off">
{/* breadcrumb row */}
<div className="relative right-5">
<div className="relative right-6">
<NavHeader pageName={t('dashboard:title')} isProjectRelated />
</div>
<div className="mt-6 ml-1">
<div className="mt-6">
<p className="text-3xl font-semibold text-bunker-100">Secrets Overview</p>
<p className="text-md text-bunker-300">
Inject your secrets using
@@ -182,16 +182,8 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
</TableContainer>
)}
{isDashboardSecretEmpty && (
<div className="mt-1 flex h-40 w-full flex-row rounded-md">
<div className="sticky top-0 flex w-10 items-center justify-center border-none px-4">
<div className="w-10 text-center text-xs text-transparent">{0}</div>
</div>
<div className="sticky top-0 border-none">
<div className="relative flex h-full w-full min-w-[200px] items-center justify-start lg:min-w-[220px] xl:min-w-[250px]">
<div className="text-sm font-medium text-transparent">Secret</div>
</div>
</div>
<div className="mx-2 flex w-full min-w-[11rem] flex-col items-center justify-center rounded-md border-none bg-mineshaft-800 text-bunker-300">
<div className="flex h-40 w-full flex-row rounded-md">
<div className="flex w-full min-w-[11rem] flex-col items-center justify-center rounded-md border-none bg-mineshaft-800 text-bunker-300">
<span className="mb-1">No secrets are available in this project yet.</span>
<span>You can go into any environment to add secrets there.</span>
</div>

View File

@@ -413,11 +413,11 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
);
return (
<div className="container mx-auto px-6 text-mineshaft-50 dark:[color-scheme:dark]">
<div className="mr-auto container px-6 text-mineshaft-50 dark:[color-scheme:dark] h-full">
<FormProvider {...method}>
<form autoComplete="off">
{/* breadcrumb row */}
<div className="relative right-5">
<div className="relative right-6 mb-6 -top-2">
<NavHeader
pageName={t('dashboard:title')}
currentEnv={
@@ -428,20 +428,18 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
onEnvChange={onEnvChange}
/>
</div>
{/* Secrets, commit and save button section */}
<div className="mt-6 flex items-center justify-between">
<div className="flex items-center space-x-4">
<h1 className="text-3xl font-semibold">
{isRollbackMode ? 'Secret Snapshot' : 'Secrets'}
</h1>
{isRollbackMode && Boolean(snapshotSecret) && (
<Tag colorSchema="green">
{new Date(snapshotSecret?.createdAt || '').toLocaleString()}
</Tag>
)}
</div>
<div className="flex items-center space-x-2">
{isRollbackMode && (
{/* This is only for rollbacks */}
{isRollbackMode &&
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<h1 className="text-3xl font-semibold">Secret Snapshot</h1>
{isRollbackMode && Boolean(snapshotSecret) && (
<Tag colorSchema="green">
{new Date(snapshotSecret?.createdAt || '').toLocaleString()}
</Tag>
)}
</div>
<div className="flex items-center space-x-2">
<Button
variant="star"
leftIcon={<FontAwesomeIcon icon={faArrowLeft} />}
@@ -453,33 +451,13 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
>
Go back
</Button>
)}
<Button
variant="star"
onClick={() => handlePopUpOpen('secretSnapshots')}
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
isLoading={isLoadingSnapshotCount}
isDisabled={!canDoRollback}
className="h-10"
>
{snapshotCount} Commits
</Button>
<Button
isDisabled={isSubmitDisabled}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={isRollbackMode ? faClockRotateLeft : faCheck} />}
onClick={handleSubmit(onSaveSecret)}
className="h-10"
>
{isRollbackMode ? 'Rollback' : 'Save Changes'}
</Button>
</div>
</div>
</div>
</div>}
{/* Environment, search and other action row */}
<div className="mt-4 flex items-center space-x-2">
<div className="flex-grow">
<div className="mt-2 flex items-center space-x-2 justify-between">
<div className="flex-grow max-w-sm">
<Input
className="h-[2.3rem] bg-mineshaft-600 placeholder-mineshaft-50"
className="h-[2.3rem] bg-mineshaft-800 focus:bg-mineshaft-700/80 duration-200 placeholder-mineshaft-50"
placeholder="Search keys..."
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
@@ -490,7 +468,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
<div>
<Popover>
<PopoverTrigger asChild>
<IconButton ariaLabel="download" variant="star">
<IconButton ariaLabel="download" variant="outline_bg">
<FontAwesomeIcon icon={faDownload} />
</IconButton>
</PopoverTrigger>
@@ -501,7 +479,8 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
<div className="flex flex-col space-y-2">
<Button
onClick={() => downloadSecret(getValues('secrets'), selectedEnv?.slug)}
variant="star"
colorSchema="primary"
variant="outline_bg"
className="h-8 bg-bunker-700"
>
Download as .env
@@ -514,45 +493,92 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
<Tooltip content={isSecretValueHidden ? 'Reveal Secrets' : 'Hide secrets'}>
<IconButton
ariaLabel="reveal"
variant="star"
variant="outline_bg"
onClick={() => setIsSecretValueHidden.toggle()}
>
<FontAwesomeIcon icon={isSecretValueHidden ? faEye : faEyeSlash} />
</IconButton>
</Tooltip>
</div>
{!isReadOnly && !isRollbackMode && (
<div className='block xl:hidden'>
<Tooltip content='Point-in-time Recovery'>
<IconButton
ariaLabel="recovery"
variant="outline_bg"
onClick={() => setIsSecretValueHidden.toggle()}
>
<FontAwesomeIcon icon={faCodeCommit} />
</IconButton>
</Tooltip>
</div>
<div className='hidden xl:block'>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (secretContainer.current) {
secretContainer.current.scroll({
top: 0,
behavior: 'smooth'
});
}
prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false });
}}
isDisabled={isReadOnly || isRollbackMode}
variant="star"
variant="outline_bg"
onClick={() => handlePopUpOpen('secretSnapshots')}
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
isLoading={isLoadingSnapshotCount}
isDisabled={!canDoRollback}
className="h-10"
>
Add Secret
{snapshotCount} Commits
</Button>
</div>
{!isReadOnly && !isRollbackMode && (
<>
<div className='block lg:hidden'>
<Tooltip content='Point-in-time Recovery'>
<IconButton
ariaLabel="recovery"
variant="outline_bg"
onClick={() => setIsSecretValueHidden.toggle()}
>
<FontAwesomeIcon icon={faPlus} />
</IconButton>
</Tooltip>
</div>
<div className='hidden lg:block'>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (secretContainer.current) {
secretContainer.current.scroll({
top: 0,
behavior: 'smooth'
});
}
prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false });
}}
isDisabled={isReadOnly || isRollbackMode}
variant="outline_bg"
className="h-10"
>
Add Secret
</Button>
</div>
</>
)}
<Button
isDisabled={isSubmitDisabled}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={isRollbackMode ? faClockRotateLeft : faCheck} />}
onClick={handleSubmit(onSaveSecret)}
className="h-10"
>
{isRollbackMode ? 'Rollback' : 'Save Changes'}
</Button>
</div>
</div>
<div
className={`${
isSecretEmpty ? 'flex flex-col items-center justify-center' : ''
} no-scrollbar::-webkit-scrollbar mt-4 h-[calc(100vh-270px)] overflow-x-hidden overflow-y-scroll no-scrollbar`}
} no-scrollbar::-webkit-scrollbar mt-3 h-[calc(100vh-220px)] overflow-x-hidden overflow-y-scroll no-scrollbar`}
ref={secretContainer}
>
{!isSecretEmpty && (
<TableContainer>
<TableContainer className="max-h-[calc(100%-40px)] no-scrollbar no-scrollbar::-webkit-scrollbar">
<table className="secret-table relative">
<SecretTableHeader sortDir={sortDir} onSort={onSortSecrets} />
<tbody className="max-h-screen overflow-y-auto">
<tbody className="max-h-96 overflow-y-auto">
{fields.map(({ id, _id }, index) => (
<SecretInputRow
key={id}
@@ -573,7 +599,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
<td colSpan={3} className="hover:bg-mineshaft-700">
<button
type="button"
className="ml-12 flex h-8 items-center justify-start font-normal text-bunker-300"
className="pl-12 cursor-default w-full flex h-8 items-center justify-start font-normal text-bunker-300"
onClick={onAppendSecret}
>
<FontAwesomeIcon icon={faPlus} />

View File

@@ -78,53 +78,55 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
};
return (
<div
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
className={twMerge(
'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 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="loading animation" />
</div>
) : (
<>
<div>
<FontAwesomeIcon icon={faUpload} size={isSmaller ? '2x' : '5x'} />
<div className="mx-1">
<div
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
className={twMerge(
'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 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100',
isDragActive && 'opacity-100',
!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="loading animation" />
</div>
<div>
<p className="">{t(isSmaller ? 'common:drop-zone-keys' : 'common:drop-zone')}</p>
</div>
<input
id="fileSelect"
type="file"
className="absolute h-full w-full cursor-pointer opacity-0"
accept=".txt,.env,.yml,.yaml"
onChange={handleFileUpload}
/>
{!isSmaller && (
<>
<div className="flex w-full flex-row items-center justify-center py-4">
<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}>
Add a new secret
</Button>
</div>
</>
)}{' '}
</>
)}
) : (
<>
<div>
<FontAwesomeIcon icon={faUpload} size={isSmaller ? '2x' : '5x'} />
</div>
<div>
<p className="">{t(isSmaller ? 'common:drop-zone-keys' : 'common:drop-zone')}</p>
</div>
<input
id="fileSelect"
type="file"
className="absolute h-full w-full cursor-pointer opacity-0"
accept=".txt,.env,.yml,.yaml"
onChange={handleFileUpload}
/>
{!isSmaller && (
<>
<div className="flex w-full flex-row items-center justify-center py-4">
<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}>
Add a new secret
</Button>
</div>
</>
)}{' '}
</>
)}
</div>
</div>
);
};

View File

@@ -6,6 +6,7 @@ import {
faComment,
faEllipsis,
faInfoCircle,
faKey,
faPlus,
faTags,
faXmark
@@ -158,8 +159,9 @@ export const SecretInputRow = memo(
return (
<tr className="group flex flex-row items-center" key={index}>
<td className="flex h-10 w-10 items-center justify-center px-4">
<div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div>
<td className="flex h-10 w-10 items-center justify-center px-4 border-none">
{/* <div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div> */}
<div className="w-10 text-center text-xs text-bunker-400"><FontAwesomeIcon icon={faKey} className="w-4 h-4 text-bunker-400/60 pl-2.5 pt-0.5" /></div>
</td>
<Controller
control={control}
@@ -184,65 +186,6 @@ export const SecretInputRow = memo(
field.onBlur();
}}
/>
<div className="flex w-max flex-row items-center justify-end">
<Tooltip content="Comment">
<div
className={`${
hasComment ? 'w-5' : 'w-0'
} mt-0.5 overflow-hidden group-hover:w-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 border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl">
<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>
</Tooltip>
{!isAddOnly && (
<div>
<Tooltip content="Override with a personal value">
<IconButton
variant="plain"
className={twMerge(
'mt-0.5 w-0 overflow-hidden p-0 group-hover:ml-1 group-hover:w-6',
isOverridden && 'ml-1 w-6 text-primary'
)}
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>
@@ -257,7 +200,7 @@ export const SecretInputRow = memo(
</HoverCard>
)}
/>
<td className="flex h-8 w-full flex-grow flex-row items-center justify-center">
<td className="flex h-10 border-none w-full flex-grow flex-row items-center justify-center border-r border-red">
<MaskedInput
isReadOnly={
isReadOnly || isRollbackMode || (isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)
@@ -283,14 +226,14 @@ export const SecretInputRow = memo(
</Tag>
))}
{!(isReadOnly || isAddOnly || isRollbackMode) && (
<div className="w-0 overflow-hidden group-hover:w-8">
<div className="overflow-hidden duration-0 ml-1">
<Popover>
<PopoverTrigger asChild>
<div>
<div className="w-0 data-[state=open]:w-6 group-hover:w-6">
<Tooltip content="Add tags">
<IconButton
variant="star"
size="xs"
variant="plain"
size="md"
ariaLabel="add-tag"
className="py-[0.42rem]"
>
@@ -348,9 +291,67 @@ export const SecretInputRow = memo(
</div>
)}
</div>
<div className="duration-0 invisible flex w-0 items-center justify-end space-x-2 overflow-hidden transition-all group-hover:visible group-hover:w-14">
<div className="flex flex-row items-center h-full pr-2">
{!isAddOnly && (
<div>
<Tooltip content="Override with a personal value">
<IconButton
variant="plain"
className={twMerge(
'mt-0.5 w-0 overflow-hidden p-0 group-hover:ml-1 group-hover:w-7',
isOverridden && 'ml-1 w-7 text-primary'
)}
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>
)}
<Tooltip content="Comment">
<div
className={`mt-0.5 overflow-hidden `}
>
<Popover>
<PopoverTrigger asChild>
<IconButton
className={twMerge(
'overflow-hidden p-0 w-7',
'data-[state=open]:w-7 group-hover:w-7 w-0',
hasComment ? 'text-primary w-7' : 'group-hover:w-7'
)}
variant="plain"
size="md"
ariaLabel="add-tag"
>
<FontAwesomeIcon icon={faComment} />
</IconButton>
</PopoverTrigger>
<PopoverContent className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl" sticky="always">
<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>
</Tooltip>
</div>
<div className="duration-0 w-0 flex items-center justify-end space-x-2.5 overflow-hidden transition-all w-16 border-l border-mineshaft-600 h-10">
{!isAddOnly && (
<div className="opacity-0 group-hover:opacity-100">
<Tooltip content="Settings">
<IconButton
size="lg"
@@ -364,7 +365,7 @@ export const SecretInputRow = memo(
</Tooltip>
</div>
)}
<div>
<div className="opacity-0 group-hover:opacity-100">
<Tooltip content="Delete">
<IconButton
size="md"

View File

@@ -9,9 +9,9 @@ type Props = {
};
export const SecretTableHeader = ({ sortDir, onSort }: Props): JSX.Element => (
<thead>
<tr className="sticky top-0 flex flex-row">
<td className="flex w-10 items-center justify-center px-4">
<thead className="sticky top-0 z-50 bg-mineshaft-800">
<tr className="top-0 flex flex-row">
<td className="flex w-10 items-center justify-center px-4 border-none">
<div className="w-10 text-center text-xs text-transparent">{0}</div>
</td>
<td className="flex items-center">