mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 16:27:40 +00:00
feat: added projects list view
This commit is contained in:
@@ -17,10 +17,14 @@ import {
|
|||||||
faClipboard,
|
faClipboard,
|
||||||
faExclamationCircle,
|
faExclamationCircle,
|
||||||
faHandPeace,
|
faHandPeace,
|
||||||
|
faList,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faNetworkWired,
|
faNetworkWired,
|
||||||
faPlug,
|
faPlug,
|
||||||
faPlus,
|
faPlus,
|
||||||
|
faProjectDiagram,
|
||||||
|
faTableCells,
|
||||||
|
faTableColumns,
|
||||||
faUserPlus
|
faUserPlus
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@@ -34,6 +38,9 @@ import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -86,6 +93,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 +321,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 +334,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 +416,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 +429,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 +447,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}`}
|
||||||
@@ -496,6 +513,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
|
||||||
@@ -551,64 +571,7 @@ const OrganizationPage = withPermission(
|
|||||||
|
|
||||||
const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0;
|
const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0;
|
||||||
|
|
||||||
return (
|
const projectsGridView = (
|
||||||
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
|
||||||
<Head>
|
|
||||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
</Head>
|
|
||||||
{!serverDetails?.redisConfigured && (
|
|
||||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
|
||||||
<p className="mr-4 mb-4 font-semibold text-white">Announcements</p>
|
|
||||||
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faExclamationCircle}
|
|
||||||
className="mr-4 p-4 text-2xl text-mineshaft-50"
|
|
||||||
/>
|
|
||||||
Attention: Updated versions of Infisical now require Redis for full functionality.
|
|
||||||
Learn how to configure it
|
|
||||||
<Link
|
|
||||||
href="https://infisical.com/docs/self-hosting/configuration/redis"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<span className="cursor-pointer pl-1 text-white underline underline-offset-2 duration-100 hover:text-blue-200 hover:decoration-blue-400">
|
|
||||||
here
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
|
||||||
<p className="mr-4 font-semibold text-white">Projects</p>
|
|
||||||
<div className="mt-6 flex w-full flex-row">
|
|
||||||
<Input
|
|
||||||
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
|
||||||
placeholder="Search by project name..."
|
|
||||||
value={searchFilter}
|
|
||||||
onChange={(e) => setSearchFilter(e.target.value)}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
|
||||||
/>
|
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Button
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
colorSchema="primary"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
onClick={() => {
|
|
||||||
if (isAddingProjectsAllowed) {
|
|
||||||
handlePopUpOpen("addNewWs");
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="ml-2"
|
|
||||||
>
|
|
||||||
Add New Project
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
|
||||||
</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">
|
<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 &&
|
{isWorkspaceLoading &&
|
||||||
Array.apply(0, Array(3)).map((_x, i) => (
|
Array.apply(0, Array(3)).map((_x, i) => (
|
||||||
@@ -655,6 +618,139 @@ const OrganizationPage = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const projectsListView = (
|
||||||
|
<div className="mt-4 w-full">
|
||||||
|
{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"
|
||||||
|
>
|
||||||
|
<Skeleton className="w-full bg-mineshaft-600" />
|
||||||
|
</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-12 cursor-pointer flex-row items-center justify-between border border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700"
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<FontAwesomeIcon icon={faProjectDiagram} className="text-sm text-white" />
|
||||||
|
<div className="ml-4 truncate text-lg text-mineshaft-100">{workspace.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{workspace.environments?.length || 0} environments
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
||||||
|
<Head>
|
||||||
|
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Head>
|
||||||
|
{!serverDetails?.redisConfigured && (
|
||||||
|
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
||||||
|
<p className="mr-4 mb-4 font-semibold text-white">Announcements</p>
|
||||||
|
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faExclamationCircle}
|
||||||
|
className="mr-4 p-4 text-2xl text-mineshaft-50"
|
||||||
|
/>
|
||||||
|
Attention: Updated versions of Infisical now require Redis for full functionality.
|
||||||
|
Learn how to configure it
|
||||||
|
<Link
|
||||||
|
href="https://infisical.com/docs/self-hosting/configuration/redis"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<span className="cursor-pointer pl-1 text-white underline underline-offset-2 duration-100 hover:text-blue-200 hover:decoration-blue-400">
|
||||||
|
here
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline_bg" leftIcon={<FontAwesomeIcon icon={faTableColumns} />}>
|
||||||
|
Display
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<div className="flex flex-col space-y-1 p-1.5">
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faTableCells} className="pr-2" />}
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID);
|
||||||
|
setProjectsViewMode(ProjectsViewMode.GRID);
|
||||||
|
}}
|
||||||
|
variant="outline_bg"
|
||||||
|
className="h-10 text-left"
|
||||||
|
isFullWidth
|
||||||
|
>
|
||||||
|
Grid
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faList} className="pr-2" />}
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST);
|
||||||
|
setProjectsViewMode(ProjectsViewMode.LIST);
|
||||||
|
}}
|
||||||
|
variant="outline_bg"
|
||||||
|
className="h-10 text-left"
|
||||||
|
isFullWidth
|
||||||
|
>
|
||||||
|
List
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 flex w-full flex-row">
|
||||||
|
<Input
|
||||||
|
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||||
|
placeholder="Search by project name..."
|
||||||
|
value={searchFilter}
|
||||||
|
onChange={(e) => setSearchFilter(e.target.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
|
/>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
colorSchema="primary"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => {
|
||||||
|
if (isAddingProjectsAllowed) {
|
||||||
|
handlePopUpOpen("addNewWs");
|
||||||
|
} else {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="ml-2"
|
||||||
|
>
|
||||||
|
Add New Project
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
{projectsViewMode === ProjectsViewMode.LIST ? projectsListView : projectsGridView}
|
||||||
{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 +871,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
|
||||||
|
|||||||
Reference in New Issue
Block a user