Merge remote-tracking branch 'origin' into cert-mgmt

This commit is contained in:
Tuan Dang
2024-06-13 14:28:04 -07:00
7 changed files with 286 additions and 142 deletions
@@ -14,6 +14,7 @@ export type Workspace = {
orgId: string; orgId: string;
version: ProjectVersion; version: ProjectVersion;
upgradeStatus: string | null; upgradeStatus: string | null;
updatedAt: string;
autoCapitalization: boolean; autoCapitalization: boolean;
environments: WorkspaceEnv[]; environments: WorkspaceEnv[];
pitVersionLimit: number; pitVersionLimit: number;
+46
View File
@@ -0,0 +1,46 @@
export const timeAgo = (inputDate: Date, currentDate: Date): string => {
const now = new Date(currentDate).getTime();
const date = new Date(inputDate).getTime();
const elapsedMilliseconds = now - date;
const elapsedSeconds = Math.abs(Math.floor(elapsedMilliseconds / 1000));
const elapsedMinutes = Math.abs(Math.floor(elapsedSeconds / 60));
const elapsedHours = Math.abs(Math.floor(elapsedMinutes / 60));
const elapsedDays = Math.abs(Math.floor(elapsedHours / 24));
const elapsedWeeks = Math.abs(Math.floor(elapsedDays / 7));
const elapsedMonths = Math.abs(Math.floor(elapsedDays / 30));
const elapsedYears = Math.abs(Math.floor(elapsedDays / 365));
if (elapsedYears > 0) {
return `${elapsedYears} year${elapsedYears === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
}
if (elapsedMonths > 0) {
return `${elapsedMonths} month${elapsedMonths === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
}
if (elapsedWeeks > 0) {
return `${elapsedWeeks} week${elapsedWeeks === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
}
if (elapsedDays > 0) {
return `${elapsedDays} day${elapsedDays === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
}
if (elapsedHours > 0) {
return `${elapsedHours} hour${elapsedHours === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
}
if (elapsedMinutes > 0) {
return `${elapsedMinutes} minute${elapsedMinutes === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
}
return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ${
elapsedMilliseconds >= 0 ? "ago" : "from now"
}`;
};
+146 -53
View File
@@ -12,11 +12,14 @@ import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { import {
faArrowRight, faArrowRight,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
faBorderAll,
faCheck, faCheck,
faCheckCircle, faCheckCircle,
faClipboard, faClipboard,
faExclamationCircle, faExclamationCircle,
faFileShield,
faHandPeace, faHandPeace,
faList,
faMagnifyingGlass, faMagnifyingGlass,
faNetworkWired, faNetworkWired,
faPlug, faPlug,
@@ -35,6 +38,7 @@ import {
Button, Button,
Checkbox, Checkbox,
FormControl, FormControl,
IconButton,
Input, Input,
Modal, Modal,
ModalContent, ModalContent,
@@ -86,6 +90,11 @@ type ItemProps = {
link?: string; link?: string;
}; };
enum ProjectsViewMode {
GRID = "grid",
LIST = "list"
}
function copyToClipboard(id: string, setState: (value: boolean) => void) { function copyToClipboard(id: string, setState: (value: boolean) => void) {
// Get the text field // Get the text field
const copyText = document.getElementById(id) as HTMLInputElement; const copyText = document.getElementById(id) as HTMLInputElement;
@@ -309,7 +318,8 @@ const LearningItem = ({
href={link} href={link}
> >
<div <div
className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : "" className={`${
complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
} mb-3 rounded-md`} } mb-3 rounded-md`}
> >
<div <div
@@ -321,7 +331,8 @@ const LearningItem = ({
await registerUserAction.mutateAsync(userAction); await registerUserAction.mutateAsync(userAction);
} }
}} }}
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${complete className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${
complete
? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700"
: "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700"
} text-mineshaft-100 duration-200`} } text-mineshaft-100 duration-200`}
@@ -402,7 +413,8 @@ const LearningItemSquare = ({
href={link} href={link}
> >
<div <div
className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : "" className={`${
complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
} w-full rounded-md`} } w-full rounded-md`}
> >
<div <div
@@ -414,7 +426,8 @@ const LearningItemSquare = ({
await registerUserAction.mutateAsync(userAction); await registerUserAction.mutateAsync(userAction);
} }
}} }}
className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${complete className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${
complete
? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700"
: "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700"
} text-mineshaft-100 duration-200`} } text-mineshaft-100 duration-200`}
@@ -431,7 +444,8 @@ const LearningItemSquare = ({
</div> </div>
)} )}
<div <div
className={`text-right text-sm font-normal text-mineshaft-300 ${complete ? "font-semibold text-primary" : "" className={`text-right text-sm font-normal text-mineshaft-300 ${
complete ? "font-semibold text-primary" : ""
}`} }`}
> >
{complete ? "Complete!" : `About ${time}`} {complete ? "Complete!" : `About ${time}`}
@@ -461,7 +475,6 @@ const formSchema = yup.object({
type TAddProjectFormData = yup.InferType<typeof formSchema>; type TAddProjectFormData = yup.InferType<typeof formSchema>;
// #TODO: Update all the workspaceIds // #TODO: Update all the workspaceIds
const OrganizationPage = withPermission( const OrganizationPage = withPermission(
() => { () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -496,6 +509,9 @@ const OrganizationPage = withPermission(
const createWs = useCreateWorkspace(); const createWs = useCreateWorkspace();
const { user } = useUser(); const { user } = useUser();
const { data: serverDetails } = useFetchServerStatus(); const { data: serverDetails } = useFetchServerStatus();
const [projectsViewMode, setProjectsViewMode] = useState<ProjectsViewMode>(
(localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID
);
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => { const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
// type check // type check
@@ -550,6 +566,95 @@ const OrganizationPage = withPermission(
}, []); }, []);
const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0; const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0;
const filteredWorkspaces = orgWorkspaces.filter((ws) =>
ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())
);
const projectsGridView = (
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
{isWorkspaceLoading &&
Array.apply(0, Array(3)).map((_x, i) => (
<div
key={`workspace-cards-loading-${i + 1}`}
className="min-w-72 flex h-40 flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="mt-0 text-lg text-mineshaft-100">
<Skeleton className="w-3/4 bg-mineshaft-600" />
</div>
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
<Skeleton className="w-1/2 bg-mineshaft-600" />
</div>
<div className="flex justify-end">
<Skeleton className="w-1/2 bg-mineshaft-600" />
</div>
</div>
))}
{filteredWorkspaces.map((workspace) => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
<div
onClick={() => {
router.push(`/project/${workspace.id}/secrets/overview`);
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id}
className="min-w-72 group flex h-40 cursor-pointer flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
{workspace.environments?.length || 0} environments
</div>
<button type="button">
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all group-hover:border-primary-500/80 group-hover:bg-primary-800/20 group-hover:text-mineshaft-200">
Explore{" "}
<FontAwesomeIcon
icon={faArrowRight}
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
/>
</div>
</button>
</div>
))}
</div>
);
const projectsListView = (
<div className="mt-4 w-full rounded-md">
{isWorkspaceLoading &&
Array.apply(0, Array(3)).map((_x, i) => (
<div
key={`workspace-cards-loading-${i + 1}`}
className={`min-w-72 group flex h-12 cursor-pointer flex-row items-center justify-between border border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
i === 0 && "rounded-t-md"
} ${i === 2 && "rounded-b-md border-b"}`}
>
<Skeleton className="w-full bg-mineshaft-600" />
</div>
))}
{filteredWorkspaces.map((workspace, ind) => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
<div
onClick={() => {
router.push(`/project/${workspace.id}/secrets/overview`);
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id}
className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
ind === 0 && "rounded-t-md"
} ${ind === filteredWorkspaces.length - 1 && "rounded-b-md border-b"}`}
>
<div className="flex items-center sm:col-span-3 lg:col-span-4">
<FontAwesomeIcon icon={faFileShield} className="text-sm text-primary/70" />
<div className="ml-5 truncate text-sm text-mineshaft-100">{workspace.name}</div>
</div>
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
<div className="text-center text-sm text-mineshaft-300">
{workspace.environments?.length || 0} environments
</div>
</div>
</div>
))}
</div>
);
return ( return (
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen"> <div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
@@ -580,7 +685,9 @@ const OrganizationPage = withPermission(
</div> </div>
)} )}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl"> <div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
<div className="flex w-full justify-between">
<p className="mr-4 font-semibold text-white">Projects</p> <p className="mr-4 font-semibold text-white">Projects</p>
</div>
<div className="mt-6 flex w-full flex-row"> <div className="mt-6 flex w-full flex-row">
<Input <Input
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80" className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
@@ -589,6 +696,36 @@ const OrganizationPage = withPermission(
onChange={(e) => setSearchFilter(e.target.value)} onChange={(e) => setSearchFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/> />
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
<IconButton
variant="outline_bg"
onClick={() => {
localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID);
setProjectsViewMode(ProjectsViewMode.GRID);
}}
ariaLabel="grid"
size="xs"
className={`${
projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent"
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
>
<FontAwesomeIcon icon={faBorderAll} />
</IconButton>
<IconButton
variant="outline_bg"
onClick={() => {
localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST);
setProjectsViewMode(ProjectsViewMode.LIST);
}}
ariaLabel="list"
size="xs"
className={`${
projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent"
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
>
<FontAwesomeIcon icon={faList} />
</IconButton>
</div>
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}> <OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
@@ -609,52 +746,7 @@ const OrganizationPage = withPermission(
)} )}
</OrgPermissionCan> </OrgPermissionCan>
</div> </div>
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"> {projectsViewMode === ProjectsViewMode.LIST ? projectsListView : projectsGridView}
{isWorkspaceLoading &&
Array.apply(0, Array(3)).map((_x, i) => (
<div
key={`workspace-cards-loading-${i + 1}`}
className="min-w-72 flex h-40 flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="mt-0 text-lg text-mineshaft-100">
<Skeleton className="w-3/4 bg-mineshaft-600" />
</div>
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
<Skeleton className="w-1/2 bg-mineshaft-600" />
</div>
<div className="flex justify-end">
<Skeleton className="w-1/2 bg-mineshaft-600" />
</div>
</div>
))}
{orgWorkspaces
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
.map((workspace) => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
<div
onClick={() => {
router.push(`/project/${workspace.id}/secrets/overview`);
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id}
className="min-w-72 group flex h-40 cursor-pointer flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
{workspace.environments?.length || 0} environments
</div>
<button type="button">
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all group-hover:border-primary-500/80 group-hover:bg-primary-800/20 group-hover:text-mineshaft-200">
Explore{" "}
<FontAwesomeIcon
icon={faArrowRight}
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
/>
</div>
</button>
</div>
))}
</div>
{isWorkspaceEmpty && ( {isWorkspaceEmpty && (
<div className="w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300"> <div className="w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
<FontAwesomeIcon <FontAwesomeIcon
@@ -775,7 +867,8 @@ const OrganizationPage = withPermission(
</div> </div>
</div> </div>
<div <div
className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green" className={`w-28 pr-4 text-right text-sm font-semibold ${
false && "text-green"
}`} }`}
> >
About 2 min About 2 min
@@ -70,6 +70,7 @@ export const MultiEnvProjectPermission = ({
}, [allRule]); }, [allRule]);
const handlePermissionChange = (val: Permission) => { const handlePermissionChange = (val: Permission) => {
if(!val) return
switch (val) { switch (val) {
case Permission.NoAccess: { case Permission.NoAccess: {
const permissions = getValue("permissions"); const permissions = getValue("permissions");
@@ -53,6 +53,7 @@ export const SecretRollbackPermission = ({ isNonEditable, setValue, control }: P
}, [selectedPermissionCategory]); }, [selectedPermissionCategory]);
const handlePermissionChange = (val: Permission) => { const handlePermissionChange = (val: Permission) => {
if(!val) return;
if (val === Permission.Custom) setIsCustom.on(); if (val === Permission.Custom) setIsCustom.on();
else setIsCustom.off(); else setIsCustom.off();
@@ -100,6 +100,7 @@ export const SingleProjectPermission = ({
}, [selectedPermissionCategory]); }, [selectedPermissionCategory]);
const handlePermissionChange = (val: Permission) => { const handlePermissionChange = (val: Permission) => {
if(!val) return;
if (val === Permission.Custom) setIsCustom.on(); if (val === Permission.Custom) setIsCustom.on();
else setIsCustom.off(); else setIsCustom.off();
@@ -52,6 +52,7 @@ export const WsProjectPermission = ({ isNonEditable, setValue, control }: Props)
}, [selectedPermissionCategory]); }, [selectedPermissionCategory]);
const handlePermissionChange = (val: Permission) => { const handlePermissionChange = (val: Permission) => {
if(!val) return;
if (val === Permission.Custom) setIsCustom.on(); if (val === Permission.Custom) setIsCustom.on();
else setIsCustom.off(); else setIsCustom.off();