mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 10:35:17 +00:00
Merge pull request #3872 from Infisical/feat/team-autonomy-product-migration
feat: project ui v3
This commit is contained in:
@@ -7,7 +7,7 @@ export const Route = createFileRoute("/_authenticate/_inject-org-details/admin/_
|
||||
beforeLoad: ({ context }) => {
|
||||
if (!context.user.superAdmin) {
|
||||
throw redirect({
|
||||
to: "/organization/secret-manager/overview"
|
||||
to: "/organization/projects"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useServerConfig } from "@app/context";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { queryClient } from "@app/hooks/api/reactQuery";
|
||||
import { userKeys } from "@app/hooks/api/users";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: string) => {
|
||||
const userOrgs = await fetchOrganizations();
|
||||
@@ -13,7 +12,7 @@ export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: s
|
||||
|
||||
if (organizationId) {
|
||||
localStorage.setItem("orgData.id", organizationId);
|
||||
navigate({ to: "/organization/secret-manager/overview" });
|
||||
navigate({ to: "/organization/projects" });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,7 +20,7 @@ export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: s
|
||||
// user is part of at least 1 non-auth enforced org
|
||||
const userOrg = nonAuthEnforcedOrgs[0] && nonAuthEnforcedOrgs[0].id;
|
||||
localStorage.setItem("orgData.id", userOrg);
|
||||
navigate({ to: `/organization/${ProjectType.SecretManager}/overview` as const });
|
||||
navigate({ to: "/organization/projects" });
|
||||
} else {
|
||||
// user is not part of any non-auth enforced orgs
|
||||
localStorage.removeItem("orgData.id");
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { isLoggedIn } from "@app/hooks/api/reactQuery";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
@@ -204,7 +203,7 @@ export const SignupInvitePage = () => {
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
to: "/organization/projects"
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
import { useServerConfig } from "@app/context";
|
||||
import { useVerifySignupEmailVerificationCode } from "@app/hooks/api";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const SignUpPage = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -73,7 +72,7 @@ export const SignUpPage = () => {
|
||||
|
||||
if (!serverDetails?.emailConfigured && step === 4) {
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
to: "/organization/projects"
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ import { useToggle } from "@app/hooks";
|
||||
import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries";
|
||||
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
@@ -207,7 +206,7 @@ export const UserInfoSSOStep = ({
|
||||
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
to: "/organization/projects"
|
||||
});
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
useGetPkiCollectionById,
|
||||
useUpdatePkiCollection
|
||||
} from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = z.object({
|
||||
@@ -83,7 +82,7 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/pki-collections/$collectionId` as const,
|
||||
to: "/projects/$projectId/cert-manager/pki-collections/$collectionId",
|
||||
params: {
|
||||
projectId,
|
||||
collectionId
|
||||
|
||||
@@ -61,7 +61,7 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`pki-collection-${pkiCollection.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/pki-collections/$collectionId` as const,
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/pki-collections/$collectionId` as const,
|
||||
params: {
|
||||
projectId,
|
||||
collectionId: pkiCollection.id
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { AlertingPage } from "./AlertingPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
|
||||
)({
|
||||
component: AlertingPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -71,7 +71,7 @@ const Page = () => {
|
||||
|
||||
handlePopUpClose("deleteCa");
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/certificate-authorities` as const,
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificate-authorities` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
|
||||
)({
|
||||
component: CertAuthDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
@@ -13,11 +13,14 @@ export const Route = createFileRoute(
|
||||
{
|
||||
label: "Certificate Authorities",
|
||||
link: linkOptions({
|
||||
to: "/cert-manager/$projectId/certificate-authorities",
|
||||
to: "/projects/$projectId/cert-manager/certificate-authorities",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: params.caName
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/ca/$caName` as const,
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/ca/$caName` as const,
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
caName: ca.name
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { CertificateAuthoritiesPage } from "./CertificateAuthoritiesPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
|
||||
)({
|
||||
component: CertificateAuthoritiesPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -3,7 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { CertificatesPage } from "./CertificatesPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
|
||||
)({
|
||||
component: CertificatesPage
|
||||
component: CertificatesPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Certificates"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ export const PkiCollectionPage = () => {
|
||||
});
|
||||
handlePopUpClose("deletePkiCollection");
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/certificates` as const,
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificates` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
|
||||
)({
|
||||
component: PkiCollectionDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
@@ -13,11 +13,14 @@ export const Route = createFileRoute(
|
||||
{
|
||||
label: "Certificate Collections",
|
||||
link: linkOptions({
|
||||
to: "/cert-manager/$projectId/certificates",
|
||||
to: "/projects/$projectId/cert-manager/certificates",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Details"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ const Page = () => {
|
||||
|
||||
handlePopUpClose("deletePkiSubscriber");
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/subscribers` as const,
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
|
||||
)({
|
||||
component: PkiSubscriberDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
@@ -13,11 +13,14 @@ export const Route = createFileRoute(
|
||||
{
|
||||
label: "Subscribers",
|
||||
link: linkOptions({
|
||||
to: "/cert-manager/$projectId/subscribers",
|
||||
to: "/projects/$projectId/cert-manager/subscribers",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: params.subscriberName
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`pki-subscriber-${subscriber.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/subscribers/$subscriberName` as const,
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers/$subscriberName` as const,
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
subscriberName: subscriber.name
|
||||
|
||||
@@ -3,7 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { PkiSubscribersPage } from "./PkiSubscribersPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/"
|
||||
)({
|
||||
component: PkiSubscribersPage
|
||||
component: PkiSubscribersPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Subscribers"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { PkiTemplateListPage } from "./PkiTemplateListPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-templates/"
|
||||
)({
|
||||
component: PkiTemplateListPage
|
||||
component: PkiTemplateListPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Certificate Templates"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,9 +3,13 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
|
||||
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||
|
||||
const tabs = [{ name: "General", key: "tab-project-general", Component: ProjectGeneralTab }];
|
||||
const tabs = [
|
||||
{
|
||||
name: "General",
|
||||
key: "tab-project-general",
|
||||
Component: () => <div className="text-white">Coming soon</div>
|
||||
}
|
||||
];
|
||||
|
||||
export const SettingsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import { ProjectOverviewChangeSection } from "@app/components/project/ProjectOverviewChangeSection";
|
||||
|
||||
import { AuditLogsRetentionSection } from "../AuditLogsRetentionSection";
|
||||
import { DeleteProjectSection } from "../DeleteProjectSection";
|
||||
|
||||
export const ProjectGeneralTab = () => {
|
||||
return (
|
||||
<div>
|
||||
<ProjectOverviewChangeSection showSlugField />
|
||||
<AuditLogsRetentionSection />
|
||||
<DeleteProjectSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SettingsPage } from "./SettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/settings"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -1,41 +1,9 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||
import { PkiManagerLayout } from "@app/layouts/PkiManagerLayout";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout"
|
||||
)({
|
||||
component: ProjectLayout,
|
||||
beforeLoad: async ({ params, context }) => {
|
||||
const project = await context.queryClient.ensureQueryData({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||
workspaceId: params.projectId
|
||||
}),
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||
});
|
||||
|
||||
return {
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Cert Managers",
|
||||
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: project.name,
|
||||
link: linkOptions({
|
||||
to: "/cert-manager/$projectId/subscribers",
|
||||
params: { projectId: project.id }
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
component: PkiManagerLayout
|
||||
});
|
||||
|
||||
@@ -3,7 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { KmipPage } from "./KmipPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/kmip"
|
||||
)({
|
||||
component: KmipPage
|
||||
component: KmipPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "KMIP"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { OverviewPage } from "./OverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/overview"
|
||||
)({
|
||||
component: OverviewPage
|
||||
component: OverviewPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Overview"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,9 +3,13 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
|
||||
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||
|
||||
const tabs = [{ name: "General", key: "tab-project-general", Component: ProjectGeneralTab }];
|
||||
const tabs = [
|
||||
{
|
||||
name: "General",
|
||||
key: "tab-project-general",
|
||||
Component: () => <div className="text-white">Coming soon...</div>
|
||||
}
|
||||
];
|
||||
|
||||
export const SettingsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -16,7 +20,7 @@ export const SettingsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title={t("settings.project.title")} />
|
||||
<PageHeader title="Settings" />
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { useProjectPermission, useSubscription, useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { useUpdateWorkspaceAuditLogsRetention } from "@app/hooks/api/workspace/queries";
|
||||
|
||||
const formSchema = z.object({
|
||||
auditLogsRetentionDays: z.coerce.number().min(0)
|
||||
});
|
||||
|
||||
type TForm = z.infer<typeof formSchema>;
|
||||
|
||||
export const AuditLogsRetentionSection = () => {
|
||||
const { mutateAsync: updateAuditLogsRetention } = useUpdateWorkspaceAuditLogsRetention();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { membership } = useProjectPermission();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { isSubmitting, isDirty },
|
||||
handleSubmit
|
||||
} = useForm<TForm>({
|
||||
resolver: zodResolver(formSchema),
|
||||
values: {
|
||||
auditLogsRetentionDays:
|
||||
currentWorkspace?.auditLogsRetentionDays ?? subscription?.auditLogsRetentionDays ?? 0
|
||||
}
|
||||
});
|
||||
|
||||
if (!currentWorkspace) return null;
|
||||
|
||||
const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => {
|
||||
try {
|
||||
if (!subscription?.auditLogs) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description: "You can only configure audit logs retention if you upgrade your plan."
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description:
|
||||
"To update your audit logs retention period to a higher value, upgrade your plan."
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await updateAuditLogsRetention({
|
||||
auditLogsRetentionDays,
|
||||
projectSlug: currentWorkspace.slug
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated audit logs retention period",
|
||||
type: "success"
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed updating audit logs retention period",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// render only for dedicated/self-hosted instances of Infisical
|
||||
if (
|
||||
window.location.origin.includes("https://app.infisical.com") ||
|
||||
window.location.origin.includes("https://gamma.infisical.com")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="text-xl font-semibold">Audit Logs Retention</p>
|
||||
</div>
|
||||
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||
Set the number of days to keep your project audit logs.
|
||||
</p>
|
||||
<form onSubmit={handleSubmit(handleAuditLogsRetentionSubmit)} autoComplete="off">
|
||||
<div className="max-w-xs">
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={0}
|
||||
name="auditLogsRetentionDays"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Number of days"
|
||||
>
|
||||
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
disabled={!isAdmin || !isDirty}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { AuditLogsRetentionSection } from "./AuditLogsRetentionSection";
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { LeaveProjectModal } from "@app/components/v2/LeaveProjectModal";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useOrganization,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useDeleteWorkspace, useGetWorkspaceUsers, useLeaveProject } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const DeleteProjectSection = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteWorkspace",
|
||||
"leaveWorkspace"
|
||||
] as const);
|
||||
|
||||
const { currentOrg } = useOrganization();
|
||||
const { hasProjectRole, membership } = useProjectPermission();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const [isDeleting, setIsDeleting] = useToggle();
|
||||
const [isLeaving, setIsLeaving] = useToggle();
|
||||
const deleteWorkspace = useDeleteWorkspace();
|
||||
const leaveProject = useLeaveProject();
|
||||
const { data: members, isPending: isMembersLoading } = useGetWorkspaceUsers(
|
||||
currentWorkspace?.id || ""
|
||||
);
|
||||
|
||||
// If isNoAccessMember is true, then the user can't read the workspace members. So we need to handle this case separately.
|
||||
const isNoAccessMember = hasProjectRole("no-access");
|
||||
|
||||
const isOnlyAdminMember = useMemo(() => {
|
||||
if (!members || !membership || !hasProjectRole("admin")) return false;
|
||||
|
||||
const adminMembers = members.filter(
|
||||
(member) => member.roles.map((r) => r.role).includes("admin") && member.id !== membership.id // exclude the current user
|
||||
);
|
||||
|
||||
return !adminMembers.length;
|
||||
}, [members, membership]);
|
||||
|
||||
const handleDeleteWorkspaceSubmit = async () => {
|
||||
setIsDeleting.on();
|
||||
try {
|
||||
if (!currentWorkspace?.id) return;
|
||||
|
||||
await deleteWorkspace.mutateAsync({
|
||||
workspaceID: currentWorkspace?.id
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted project",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
});
|
||||
handlePopUpClose("deleteWorkspace");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete project",
|
||||
type: "error"
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting.off();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLeaveWorkspaceSubmit = async () => {
|
||||
try {
|
||||
setIsLeaving.on();
|
||||
|
||||
if (!currentWorkspace?.id || !currentOrg?.id) return;
|
||||
|
||||
// If there's no members, and the user has access to read members, something went wrong.
|
||||
if (!members && !isNoAccessMember) return;
|
||||
|
||||
// If the user has elevated permissions and can read members:
|
||||
if (!isNoAccessMember) {
|
||||
if (!members) return;
|
||||
|
||||
if (members.length < 2) {
|
||||
createNotification({
|
||||
text: "You can't leave the project as you are the only member",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
// If the user has access to read members, and there's less than 1 admin member excluding the current user, they can't leave the project.
|
||||
if (isOnlyAdminMember) {
|
||||
createNotification({
|
||||
text: "You can't leave a project with no admin members left. Promote another member to admin first.",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's actually a no-access member, then we don't really care about the members.
|
||||
|
||||
await leaveProject.mutateAsync({
|
||||
workspaceId: currentWorkspace.id
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to leave project",
|
||||
type: "error"
|
||||
});
|
||||
} finally {
|
||||
setIsLeaving.off();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
|
||||
<div className="space-x-4">
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Project}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isLoading={isDeleting}
|
||||
isDisabled={!isAllowed || isDeleting}
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("deleteWorkspace")}
|
||||
>
|
||||
{`Delete ${currentWorkspace?.name}`}
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
{!isOnlyAdminMember && (
|
||||
<Button
|
||||
disabled={isMembersLoading || (members && members?.length < 2)}
|
||||
isLoading={isLeaving}
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("leaveWorkspace")}
|
||||
>
|
||||
{`Leave ${currentWorkspace?.name}`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteWorkspace.isOpen}
|
||||
title="Are you sure you want to delete this project?"
|
||||
subTitle={`Permanently delete ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
|
||||
deleteKey="confirm"
|
||||
buttonText="Delete Project"
|
||||
onDeleteApproved={handleDeleteWorkspaceSubmit}
|
||||
/>
|
||||
|
||||
<LeaveProjectModal
|
||||
isOpen={popUp.leaveWorkspace.isOpen}
|
||||
title="Are you sure you want to leave this project?"
|
||||
subTitle={`If you leave ${currentWorkspace?.name} you will lose access to the project and its contents.`}
|
||||
onChange={(isOpen) => handlePopUpToggle("leaveWorkspace", isOpen)}
|
||||
deleteKey="confirm"
|
||||
buttonText="Leave Project"
|
||||
onLeaveApproved={handleLeaveWorkspaceSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||
@@ -1 +0,0 @@
|
||||
export { ProjectGeneralTab } from "./ProjectGeneralTab";
|
||||
@@ -1 +0,0 @@
|
||||
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SettingsPage } from "./SettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/settings"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -1,41 +1,9 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||
import { KmsLayout } from "@app/layouts/KmsLayout";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout"
|
||||
)({
|
||||
component: ProjectLayout,
|
||||
beforeLoad: async ({ params, context }) => {
|
||||
const project = await context.queryClient.ensureQueryData({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||
workspaceId: params.projectId
|
||||
}),
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||
});
|
||||
|
||||
return {
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "KMS",
|
||||
link: linkOptions({ to: "/organization/kms/overview" })
|
||||
},
|
||||
{
|
||||
label: project.name,
|
||||
link: linkOptions({
|
||||
to: "/kms/$projectId/overview",
|
||||
params: { projectId: project.id }
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
component: KmsLayout
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import { SessionStorageKeys } from "@app/const";
|
||||
import { useServerConfig } from "@app/context";
|
||||
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
||||
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const QueryParamsSchema = z.object({
|
||||
callback_port: z.coerce.number().optional().catch(undefined),
|
||||
@@ -112,7 +111,7 @@ export const Route = createFileRoute("/_restrict-login-signup")({
|
||||
throw redirect({ to: "/login/select-organization" });
|
||||
}
|
||||
throw redirect({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
to: "/organization/projects"
|
||||
});
|
||||
},
|
||||
component: AuthConsentWrapper
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -24,11 +22,6 @@ export const Route = createFileRoute(
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: "Access Control"
|
||||
}
|
||||
|
||||
+4
-21
@@ -29,6 +29,7 @@ import {
|
||||
OrgPermissionAdminConsoleAction,
|
||||
OrgPermissionSubjects
|
||||
} from "@app/context/OrgPermissionContext/types";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { useDebounce } from "@app/hooks";
|
||||
import { useOrgAdminAccessProject, useOrgAdminGetProjects } from "@app/hooks/api";
|
||||
@@ -58,26 +59,8 @@ export const OrgAdminProjects = withPermission(
|
||||
await orgAdminAccessProject.mutateAsync({
|
||||
projectId
|
||||
});
|
||||
if (type === ProjectType.CertificateManager) {
|
||||
await navigate({
|
||||
to: "/cert-manager/$projectId/subscribers" as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === ProjectType.SecretScanning) {
|
||||
await navigate({
|
||||
to: "/secret-scanning/$projectId/data-sources" as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
await navigate({
|
||||
to: `/${type}/$projectId/overview` as const,
|
||||
to: getProjectHomePage(type),
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
@@ -122,7 +105,7 @@ export const OrgAdminProjects = withPermission(
|
||||
<TBody>
|
||||
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
|
||||
{!isProjectsLoading &&
|
||||
projects?.map(({ name, slug, createdAt, type, id }) => (
|
||||
projects?.map(({ name, slug, createdAt, id, defaultProduct }) => (
|
||||
<Tr key={`project-${id}`} className="group w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{slug}</Td>
|
||||
@@ -143,7 +126,7 @@ export const OrgAdminProjects = withPermission(
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleAccessProject(type, id);
|
||||
handleAccessProject(defaultProduct, id);
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faSignIn} />}
|
||||
disabled={
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { AdminPage } from "./AdminPage";
|
||||
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: AdminPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Admin Console"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionsPage } from "./AppConnectionsPage";
|
||||
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: AppConnectionsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "App Connections"
|
||||
}
|
||||
|
||||
@@ -418,10 +418,10 @@ export const LogsFilter = ({
|
||||
onChange(e);
|
||||
}}
|
||||
placeholder="All projects"
|
||||
options={workspacesInOrg.map(({ name, id, type }) => ({
|
||||
options={workspacesInOrg.map(({ name, id, defaultProduct }) => ({
|
||||
name,
|
||||
id,
|
||||
type
|
||||
type: defaultProduct
|
||||
}))}
|
||||
getOptionValue={(option) => option.id}
|
||||
getOptionLabel={(option) => option.name}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { AuditLogsPage } from "./AuditLogsPage";
|
||||
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: AuditLogsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Audit Logs"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { OrgPermissionBillingActions, OrgPermissionSubjects } from "@app/context";
|
||||
|
||||
import { BillingTabGroup } from "./components";
|
||||
@@ -18,10 +19,11 @@ export const BillingPage = () => {
|
||||
</Helmet>
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl px-6">
|
||||
<div className="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">{t("billing.title")}</p>
|
||||
<div />
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t("billing.title")}
|
||||
description="View your billing plan, next billing cycle."
|
||||
/>
|
||||
|
||||
<OrgPermissionCan
|
||||
passThrough={false}
|
||||
I={OrgPermissionBillingActions.Read}
|
||||
|
||||
@@ -5,5 +5,14 @@ import { BillingPage } from "./BillingPage";
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/billing"
|
||||
)({
|
||||
component: BillingPage
|
||||
component: BillingPage,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Billing"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverviewPage } from "../SecretManagerOverviewPage/SecretManagerOverviewPage";
|
||||
|
||||
export const CertManagerOverviewPage = () => (
|
||||
<ProductOverviewPage type={ProjectType.CertificateManager} />
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview"
|
||||
)({
|
||||
component: CertManagerOverviewPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "Cert Management",
|
||||
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
export const CertManagerSettingsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Cert Management Settings</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Cert Management Settings" />
|
||||
<ProjectSettings />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { CertManagerSettingsPage } from "./CertManagerSettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings"
|
||||
)({
|
||||
component: CertManagerSettingsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "Cert Management",
|
||||
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { GatewayListPage } from "./GatewayListPage";
|
||||
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: GatewayListPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Gateways"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: GroupDetailsByIDPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({ to: "/organization/access-management" })
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ type Props = {
|
||||
|
||||
const Content = ({ identityId, handlePopUpToggle }: Omit<Props, "popUp">) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { data: workspaces = [] } = useGetUserWorkspaces({ type: "all" });
|
||||
const { data: workspaces = [] } = useGetUserWorkspaces();
|
||||
const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace();
|
||||
|
||||
const {
|
||||
|
||||
+2
-5
@@ -5,7 +5,7 @@ import { useNavigate } from "@tanstack/react-router";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { formatProjectRoleName } from "@app/helpers/roles";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { IdentityMembership } from "@app/hooks/api/identities/types";
|
||||
@@ -50,7 +50,7 @@ export const IdentityProjectRow = ({
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
navigate({
|
||||
to: `/${project?.type}/$projectId/access-management` as const,
|
||||
to: "/projects/$projectId/access-management",
|
||||
params: {
|
||||
projectId: project.id
|
||||
},
|
||||
@@ -68,9 +68,6 @@ export const IdentityProjectRow = ({
|
||||
}}
|
||||
>
|
||||
<Td className="max-w-0 truncate">{project.name}</Td>
|
||||
<Td>
|
||||
<Tag size="xs">{project.type}</Tag>
|
||||
</Td>
|
||||
<Td>{`${formatProjectRoleName(roles[0].role, roles[0].customRoleName)}${
|
||||
roles.length > 1 ? ` (+${roles.length - 1})` : ""
|
||||
}`}</Td>
|
||||
|
||||
-1
@@ -118,7 +118,6 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
|
||||
</div>
|
||||
</Th>
|
||||
|
||||
<Th>Type</Th>
|
||||
<Th>Role</Th>
|
||||
<Th>Added On</Th>
|
||||
<Th className="w-5" />
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: IdentityDetailsByIDPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({ to: "/organization/access-management" })
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverviewPage } from "../SecretManagerOverviewPage/SecretManagerOverviewPage";
|
||||
|
||||
export const KmsOverviewPage = () => <ProductOverviewPage type={ProjectType.KMS} />;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { KmsOverviewPage } from "./KmsOverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/kms/overview"
|
||||
)({
|
||||
component: KmsOverviewPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "KMS",
|
||||
link: linkOptions({ to: "/organization/kms/overview" })
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
export const KmsSettingsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>KMS Settings</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="KMS Settings" />
|
||||
<ProjectSettings />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { KmsSettingsPage } from "./KmsSettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/kms/settings"
|
||||
)({
|
||||
component: KmsSettingsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "KMS",
|
||||
link: linkOptions({ to: "/organization/kms/overview" })
|
||||
},
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
+13
-26
@@ -10,31 +10,25 @@ import { NewProjectModal } from "@app/components/projects";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { AllProjectView } from "./components/AllProjectView";
|
||||
import { MyProjectView } from "./components/MyProjectView";
|
||||
import { ProjectListToggle, ProjectListView } from "./components/ProjectListToggle";
|
||||
|
||||
const formatDescription = (type: ProjectType) => {
|
||||
if (type === ProjectType.SecretManager)
|
||||
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
||||
if (type === ProjectType.CertificateManager)
|
||||
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
||||
if (type === ProjectType.KMS)
|
||||
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
||||
if (type === ProjectType.SecretScanning)
|
||||
return "Connect and monitor data sources to prevent secret leaks.";
|
||||
return "Infisical SSH lets you issue SSH credentials to users for short-lived, secure SSH access to infrastructure.";
|
||||
};
|
||||
// const formatDescription = (type: ProjectType) => {
|
||||
// if (type === ProjectType.SecretManager)
|
||||
// return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
||||
// if (type === ProjectType.CertificateManager)
|
||||
// return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
||||
// if (type === ProjectType.KMS)
|
||||
// return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
||||
// if (type === ProjectType.SecretScanning)
|
||||
// return "Connect and monitor data sources to prevent secret leaks.";
|
||||
// return "Infisical SSH lets you issue SSH credentials to users for short-lived, secure SSH access to infrastructure.";
|
||||
// };
|
||||
|
||||
type Props = {
|
||||
type: ProjectType;
|
||||
};
|
||||
|
||||
// #TODO: Update all the workspaceIds
|
||||
export const ProductOverviewPage = ({ type }: Props) => {
|
||||
export const ProjectsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [projectListView, setProjectListView] = useState(ProjectListView.MyProjects);
|
||||
@@ -87,19 +81,17 @@ export const ProductOverviewPage = ({ type }: Props) => {
|
||||
<ProjectListToggle value={projectListView} onChange={setProjectListView} />
|
||||
</div>
|
||||
}
|
||||
description={formatDescription(type)}
|
||||
description="Your team's complete security toolkit - organized and ready when you need them."
|
||||
/>
|
||||
</div>
|
||||
{projectListView === ProjectListView.MyProjects ? (
|
||||
<MyProjectView
|
||||
type={type}
|
||||
onAddNewProject={() => handlePopUpOpen("addNewWs")}
|
||||
onUpgradePlan={() => handlePopUpOpen("upgradePlan")}
|
||||
isAddingProjectsAllowed={isAddingProjectsAllowed}
|
||||
/>
|
||||
) : (
|
||||
<AllProjectView
|
||||
type={type}
|
||||
onAddNewProject={() => handlePopUpOpen("addNewWs")}
|
||||
onUpgradePlan={() => handlePopUpOpen("upgradePlan")}
|
||||
isAddingProjectsAllowed={isAddingProjectsAllowed}
|
||||
@@ -108,7 +100,6 @@ export const ProductOverviewPage = ({ type }: Props) => {
|
||||
<NewProjectModal
|
||||
isOpen={popUp.addNewWs.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
|
||||
projectType={type}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
@@ -118,7 +109,3 @@ export const ProductOverviewPage = ({ type }: Props) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SecretManagerOverviewPage = () => (
|
||||
<ProductOverviewPage type={ProjectType.SecretManager} />
|
||||
);
|
||||
+3
-6
@@ -35,10 +35,9 @@ import {
|
||||
} from "@app/helpers/userTablePreferences";
|
||||
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type Props = {
|
||||
type: ProjectType;
|
||||
onAddNewProject: () => void;
|
||||
onUpgradePlan: () => void;
|
||||
isAddingProjectsAllowed: boolean;
|
||||
@@ -92,7 +91,6 @@ const RequestAccessModal = ({ projectId, onPopUpToggle }: RequestAccessModalProp
|
||||
};
|
||||
|
||||
export const AllProjectView = ({
|
||||
type,
|
||||
onAddNewProject,
|
||||
onUpgradePlan,
|
||||
isAddingProjectsAllowed
|
||||
@@ -125,7 +123,6 @@ export const AllProjectView = ({
|
||||
const { data: searchedProjects, isPending: isProjectLoading } = useSearchProjects({
|
||||
limit,
|
||||
offset,
|
||||
type,
|
||||
name: debouncedSearch || undefined,
|
||||
orderDirection
|
||||
});
|
||||
@@ -225,7 +222,7 @@ export const AllProjectView = ({
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter" && workspace.isMember) {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace),
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
@@ -235,7 +232,7 @@ export const AllProjectView = ({
|
||||
onClick={() => {
|
||||
if (workspace.isMember) {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace),
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
+4
-18
@@ -29,10 +29,9 @@ import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type Props = {
|
||||
type: ProjectType;
|
||||
onAddNewProject: () => void;
|
||||
onUpgradePlan: () => void;
|
||||
isAddingProjectsAllowed: boolean;
|
||||
@@ -48,7 +47,6 @@ enum ProjectsViewMode {
|
||||
}
|
||||
|
||||
export const MyProjectView = ({
|
||||
type,
|
||||
onAddNewProject,
|
||||
onUpgradePlan,
|
||||
isAddingProjectsAllowed
|
||||
@@ -56,9 +54,7 @@ export const MyProjectView = ({
|
||||
const navigate = useNavigate();
|
||||
const { currentOrg } = useOrganization();
|
||||
|
||||
const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserWorkspaces({
|
||||
type
|
||||
});
|
||||
const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserWorkspaces();
|
||||
const {
|
||||
setPage,
|
||||
perPage,
|
||||
@@ -157,7 +153,7 @@ export const MyProjectView = ({
|
||||
<div
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace),
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
@@ -202,11 +198,6 @@ export const MyProjectView = ({
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-row items-end justify-between place-self-end">
|
||||
{type === ProjectType.SecretManager && (
|
||||
<div className="mt-0 text-xs text-mineshaft-400">
|
||||
{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 px-4 py-2 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200">
|
||||
Explore{" "}
|
||||
@@ -224,7 +215,7 @@ export const MyProjectView = ({
|
||||
<div
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace),
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
@@ -239,11 +230,6 @@ export const MyProjectView = ({
|
||||
<div className="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">
|
||||
{type === ProjectType.SecretManager && (
|
||||
<div className="text-center text-sm text-mineshaft-300">
|
||||
{workspace.environments?.length || 0} environments
|
||||
</div>
|
||||
)}
|
||||
{isFavorite ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSolidStar}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { ProjectsPage } from "./ProjectsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/projects"
|
||||
)({
|
||||
component: ProjectsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Projects"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { RoleByIDPage } from "./RoleByIDPage";
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: RoleByIDPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({ to: "/organization/access-management" })
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview"
|
||||
)({
|
||||
component: SecretManagerOverviewPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "Secret Management"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
export const SecretManagerSettingsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Secret Management Settings</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Secret Management Settings" />
|
||||
<ProjectSettings />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { SecretManagerSettingsPage } from "./SecretManagerSettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings"
|
||||
)({
|
||||
component: SecretManagerSettingsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "Secret Management",
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverviewPage } from "../SecretManagerOverviewPage/SecretManagerOverviewPage";
|
||||
|
||||
export const SecretScanningOverviewPage = () => (
|
||||
<ProductOverviewPage type={ProjectType.SecretScanning} />
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { SecretScanningOverviewPage } from "./SecretScanningOverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning/overview"
|
||||
)({
|
||||
component: SecretScanningOverviewPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "Secret Scanning",
|
||||
link: linkOptions({ to: "/organization/secret-scanning/overview" })
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
export const SecretScanningSettingsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Secret Scanning Settings</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Secret Scanning Settings" />
|
||||
<ProjectSettings />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { SecretScanningSettingsPage } from "./SecretScanningSettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning/settings"
|
||||
)({
|
||||
component: SecretScanningSettingsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "Secret Scanning",
|
||||
link: linkOptions({ to: "/organization/secret-scanning/overview" })
|
||||
},
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -21,11 +19,6 @@ export const Route = createFileRoute(
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: "Secret Sharing"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
@@ -20,11 +18,6 @@ export const Route = createFileRoute(
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: "Secret Sharing",
|
||||
link: linkOptions({ to: "/organization/secret-sharing" })
|
||||
|
||||
+5
-9
@@ -3,18 +3,14 @@ import { useOrgPermission } from "@app/context";
|
||||
import { OrgDeleteSection } from "../OrgDeleteSection";
|
||||
import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection";
|
||||
import { OrgNameChangeSection } from "../OrgNameChangeSection";
|
||||
import { OrgProductSelectSection } from "../OrgProductSelectSection";
|
||||
|
||||
export const OrgGeneralTab = () => {
|
||||
const { membership } = useOrgPermission();
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||
<OrgNameChangeSection />
|
||||
<OrgIncidentContactsSection />
|
||||
{membership && membership.role === "admin" && <OrgDeleteSection />}
|
||||
</div>
|
||||
{membership && membership.role === "admin" && <OrgProductSelectSection />}
|
||||
</>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||
<OrgNameChangeSection />
|
||||
<OrgIncidentContactsSection />
|
||||
{membership && membership.role === "admin" && <OrgDeleteSection />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-58
@@ -1,11 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||
import { Link, useSearch } from "@tanstack/react-router";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||
import { ImportTab } from "../ImportTab";
|
||||
@@ -14,6 +11,7 @@ import { OrgEncryptionTab } from "../OrgEncryptionTab";
|
||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||
import { OrgSecurityTab } from "../OrgSecurityTab";
|
||||
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
|
||||
import { ProjectTemplatesTab } from "../ProjectTemplatesTab";
|
||||
|
||||
export const OrgTabGroup = () => {
|
||||
const search = useSearch({
|
||||
@@ -33,60 +31,7 @@ export const OrgTabGroup = () => {
|
||||
{
|
||||
name: "Project Templates",
|
||||
key: "project-templates",
|
||||
// scott: temporary, remove once users have adjusted
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
component: () => (
|
||||
<div>
|
||||
<NoticeBannerV2
|
||||
className="mx-auto"
|
||||
titleClassName="text-base"
|
||||
title="Project Templates Relocated"
|
||||
>
|
||||
<p className="mt-1 text-mineshaft-300">
|
||||
Project templates have been relocated and are now product specific:
|
||||
</p>
|
||||
<ul className="mb-1 flex gap-x-4 text-mineshaft-200">
|
||||
{[
|
||||
{
|
||||
type: ProjectType.SecretManager,
|
||||
label: "Secret Management",
|
||||
icon: "sliding-carousel"
|
||||
},
|
||||
{
|
||||
type: ProjectType.CertificateManager,
|
||||
label: "Certificate Management",
|
||||
icon: "note"
|
||||
},
|
||||
{
|
||||
type: ProjectType.KMS,
|
||||
label: "KMS",
|
||||
icon: "unlock"
|
||||
},
|
||||
{
|
||||
type: ProjectType.SSH,
|
||||
label: "SSH",
|
||||
icon: "verified"
|
||||
}
|
||||
].map(({ label, type, icon }, index) => (
|
||||
<li key={`project-template-${type}`}>
|
||||
<Link
|
||||
to={`/organization/${type}/settings`}
|
||||
className="mt-1 flex items-center gap-x-2 hover:text-mineshaft-100"
|
||||
>
|
||||
{index !== 0 && <span className="text-mineshaft-300">•</span>}
|
||||
<DotLottieReact
|
||||
src={`/lotties/${icon}.json`}
|
||||
loop
|
||||
className="mt-0.5 h-5 w-5"
|
||||
/>{" "}
|
||||
<span className="underline underline-offset-2">{label}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</NoticeBannerV2>
|
||||
</div>
|
||||
)
|
||||
component: ProjectTemplatesTab
|
||||
},
|
||||
{ name: "KMIP", key: "kmip", component: KmipTab }
|
||||
];
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
import { ProjectTemplatesSection } from "./components";
|
||||
|
||||
export const ProjectTemplatesTab = withPermission(() => <ProjectTemplatesSection />, {
|
||||
action: OrgPermissionActions.Read,
|
||||
subject: OrgPermissionSubjects.ProjectTemplates
|
||||
});
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { DeleteActionModal } from "@app/components/v2";
|
||||
import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
|
||||
type Props = {
|
||||
template?: TProjectTemplate;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
export const DeleteProjectTemplateModal = ({ isOpen, onOpenChange, template }: Props) => {
|
||||
const deleteTemplate = useDeleteProjectTemplate();
|
||||
|
||||
if (!template) return null;
|
||||
|
||||
const { id: templateId, name } = template;
|
||||
|
||||
const handleDeleteProjectTemplate = async () => {
|
||||
try {
|
||||
await deleteTemplate.mutateAsync({
|
||||
templateId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed project template",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
createNotification({
|
||||
text: "Failed remove project template",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DeleteActionModal
|
||||
isOpen={isOpen}
|
||||
onChange={onOpenChange}
|
||||
title={`Are you sure you want to delete ${name}?`}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleDeleteProjectTemplate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button, EmptyState, Spinner } from "@app/components/v2";
|
||||
import {
|
||||
InfisicalProjectTemplate,
|
||||
TProjectTemplate,
|
||||
useGetProjectTemplateById
|
||||
} from "@app/hooks/api/projectTemplates";
|
||||
|
||||
import { EditProjectTemplate } from "./components";
|
||||
|
||||
type Props = {
|
||||
template: TProjectTemplate;
|
||||
onBack: () => void;
|
||||
};
|
||||
|
||||
export const EditProjectTemplateSection = ({ template, onBack }: Props) => {
|
||||
const isInfisicalTemplate = Object.values(InfisicalProjectTemplate).includes(
|
||||
template.name as InfisicalProjectTemplate
|
||||
);
|
||||
|
||||
const { data: projectTemplate, isPending } = useGetProjectTemplateById(template.id, {
|
||||
initialData: template,
|
||||
enabled: !isInfisicalTemplate
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
variant="link"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={onBack}
|
||||
className="mb-4"
|
||||
>
|
||||
Back to Templates
|
||||
</Button>
|
||||
{/* eslint-disable-next-line no-nested-ternary */}
|
||||
{isPending ? (
|
||||
<div className="flex h-[60vh] w-full items-center justify-center p-24">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : projectTemplate ? (
|
||||
<EditProjectTemplate
|
||||
isInfisicalTemplate={isInfisicalTemplate}
|
||||
projectTemplate={projectTemplate}
|
||||
onBack={onBack}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState title="Error: Unable to find project template." className="py-12" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { faPencil, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
|
||||
import { ProjectTemplateDetailsModal } from "../../ProjectTemplateDetailsModal";
|
||||
import { ProjectTemplateEnvironmentsForm } from "./ProjectTemplateEnvironmentsForm";
|
||||
import { ProjectTemplateRolesSection } from "./ProjectTemplateRolesSection";
|
||||
|
||||
type Props = {
|
||||
projectTemplate: TProjectTemplate;
|
||||
onBack: () => void;
|
||||
isInfisicalTemplate: boolean;
|
||||
};
|
||||
|
||||
export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBack }: Props) => {
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"removeTemplate",
|
||||
"editDetails"
|
||||
] as const);
|
||||
|
||||
const { id: templateId, name, description } = projectTemplate;
|
||||
|
||||
const deleteProjectTemplate = useDeleteProjectTemplate();
|
||||
|
||||
const handleRemoveTemplate = async () => {
|
||||
try {
|
||||
await deleteProjectTemplate.mutateAsync({
|
||||
templateId
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully removed project template",
|
||||
type: "success"
|
||||
});
|
||||
onBack();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
text: "Failed to remove project template",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
handlePopUpClose("removeTemplate");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex items-start justify-between border-b border-bunker-400 pb-4">
|
||||
<div className="flex-col">
|
||||
<h3 className="text-xl font-semibold">{name}</h3>
|
||||
<h2 className="text-sm text-mineshaft-400">{description || "Project Template"}</h2>
|
||||
</div>
|
||||
{!isInfisicalTemplate && (
|
||||
<div className="flex gap-2">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
leftIcon={<FontAwesomeIcon icon={faPencil} />}
|
||||
size="xs"
|
||||
colorSchema="secondary"
|
||||
onClick={() => handlePopUpOpen("editDetails")}
|
||||
>
|
||||
Edit Details
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("removeTemplate");
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
||||
size="xs"
|
||||
colorSchema="danger"
|
||||
>
|
||||
Delete Template
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ProjectTemplateEnvironmentsForm
|
||||
isInfisicalTemplate={isInfisicalTemplate}
|
||||
projectTemplate={projectTemplate}
|
||||
/>
|
||||
<ProjectTemplateRolesSection
|
||||
isInfisicalTemplate={isInfisicalTemplate}
|
||||
projectTemplate={projectTemplate}
|
||||
/>
|
||||
<ProjectTemplateDetailsModal
|
||||
isOpen={popUp.editDetails.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("editDetails", isOpen)}
|
||||
projectTemplate={projectTemplate}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.removeTemplate.isOpen}
|
||||
title={`Are you sure you want to delete ${projectTemplate.name}?`}
|
||||
deleteKey="confirm"
|
||||
onChange={(isOpen) => handlePopUpToggle("removeTemplate", isOpen)}
|
||||
onDeleteApproved={handleRemoveTemplate}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { faChevronLeft, faSave } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { isCustomProjectRole } from "@app/helpers/roles";
|
||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
import { AddPoliciesButton } from "@app/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton";
|
||||
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
|
||||
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
|
||||
import {
|
||||
formRolePermission2API,
|
||||
PROJECT_PERMISSION_OBJECT,
|
||||
projectRoleFormSchema,
|
||||
rolePermission2Form
|
||||
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
|
||||
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
|
||||
|
||||
type Props = {
|
||||
projectTemplate: TProjectTemplate;
|
||||
role?: TProjectTemplate["roles"][number];
|
||||
onGoBack: () => void;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
slug: slugSchema(),
|
||||
name: z.string().trim().min(1),
|
||||
permissions: projectRoleFormSchema.shape.permissions
|
||||
});
|
||||
|
||||
type TFormSchema = z.infer<typeof formSchema>;
|
||||
|
||||
export const ProjectTemplateEditRoleForm = ({
|
||||
onGoBack,
|
||||
projectTemplate,
|
||||
role,
|
||||
isDisabled
|
||||
}: Props) => {
|
||||
const formMethods = useForm<TFormSchema>({
|
||||
values: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : undefined,
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isDirty, isSubmitting }
|
||||
} = formMethods;
|
||||
|
||||
const updateProjectTemplate = useUpdateProjectTemplate();
|
||||
|
||||
const onSubmit = async (form: TFormSchema) => {
|
||||
try {
|
||||
await updateProjectTemplate.mutateAsync({
|
||||
templateId: projectTemplate.id,
|
||||
roles: [
|
||||
...projectTemplate.roles.filter(
|
||||
(r) => r.slug !== role?.slug && isCustomProjectRole(r.slug) // filter out default roles as well
|
||||
),
|
||||
{
|
||||
...form,
|
||||
permissions: formRolePermission2API(form.permissions)
|
||||
}
|
||||
]
|
||||
});
|
||||
onGoBack();
|
||||
createNotification({
|
||||
text: "Template roles successfully updated",
|
||||
type: "success"
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
createNotification({
|
||||
text: "Failed to update template roles",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
>
|
||||
<FormProvider {...formMethods}>
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
className="text-base font-semibold text-mineshaft-200"
|
||||
variant="link"
|
||||
onClick={onGoBack}
|
||||
>
|
||||
{isDisabled ? "Back" : "Cancel"}
|
||||
</Button>
|
||||
{!isDisabled && (
|
||||
<div className="flex items-center space-x-4">
|
||||
{isDirty && (
|
||||
<Button
|
||||
className="mr-4 text-mineshaft-300"
|
||||
variant="link"
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
onClick={onGoBack}
|
||||
>
|
||||
Discard
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
className={twMerge(
|
||||
"mr-4 h-10 border border-primary",
|
||||
isDirty && "bg-primary text-black"
|
||||
)}
|
||||
isDisabled={isSubmitting || !isDirty || isDisabled}
|
||||
isLoading={isSubmitting}
|
||||
leftIcon={<FontAwesomeIcon icon={faSave} />}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<AddPoliciesButton isDisabled={isDisabled} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 border-b border-gray-800 p-4 pt-2 first:rounded-t-md last:rounded-b-md">
|
||||
{isDisabled ? (
|
||||
<div className="flex flex-col">
|
||||
<span className="text-lg font-semibold">{role?.name}</span>
|
||||
<span className="text-mineshaft-400">{role?.slug}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Name"
|
||||
className="mb-0 flex-1"
|
||||
>
|
||||
<Input {...field} autoFocus placeholder="Role name..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="slug"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Slug"
|
||||
className="mb-0 flex-1"
|
||||
>
|
||||
<Input {...field} placeholder="Role slug..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="mb-2 text-lg">Policies</div>
|
||||
<PermissionEmptyState />
|
||||
<div>
|
||||
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => (
|
||||
<GeneralPermissionPolicies
|
||||
subject={subject}
|
||||
actions={PROJECT_PERMISSION_OBJECT[subject].actions}
|
||||
title={PROJECT_PERMISSION_OBJECT[subject].title}
|
||||
key={`project-permission-${subject}`}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{renderConditionalComponents(subject, isDisabled)}
|
||||
</GeneralPermissionPolicies>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faArrowDown, faArrowUp, faPlus, faSave, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Table,
|
||||
TableContainer,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
type Props = {
|
||||
projectTemplate: TProjectTemplate;
|
||||
isInfisicalTemplate: boolean;
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
environments: z
|
||||
.object({
|
||||
name: z.string().trim().min(1),
|
||||
slug: slugSchema({ min: 1, max: 64 })
|
||||
})
|
||||
.array()
|
||||
.nullish()
|
||||
});
|
||||
|
||||
type TFormSchema = z.infer<typeof formSchema>;
|
||||
|
||||
export const ProjectTemplateEnvironmentsForm = ({
|
||||
projectTemplate,
|
||||
isInfisicalTemplate
|
||||
}: Props) => {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isDirty, errors },
|
||||
reset
|
||||
} = useForm<TFormSchema>({
|
||||
defaultValues: {
|
||||
environments: projectTemplate.environments
|
||||
},
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const {
|
||||
fields: environments,
|
||||
move,
|
||||
remove,
|
||||
append
|
||||
} = useFieldArray({ control, name: "environments" });
|
||||
|
||||
const updateProjectTemplate = useUpdateProjectTemplate();
|
||||
|
||||
const onFormSubmit = async (form: TFormSchema) => {
|
||||
try {
|
||||
const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({
|
||||
environments: form.environments?.map((env, index) => ({
|
||||
...env,
|
||||
position: index + 1
|
||||
})),
|
||||
templateId: projectTemplate.id
|
||||
});
|
||||
|
||||
reset({ environments: updatedEnvs });
|
||||
|
||||
createNotification({
|
||||
text: "Project template updated successfully",
|
||||
type: "success"
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
createNotification({
|
||||
text: e.message ?? "Failed to update project template",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isEnvironmentLimitExceeded =
|
||||
Boolean(subscription.environmentLimit) && environments.length >= subscription.environmentLimit;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Project Environments</h2>
|
||||
{!isInfisicalTemplate && (
|
||||
<p className="text-sm text-mineshaft-400">
|
||||
Add, rename, remove and reorder environments for this project template
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{!isInfisicalTemplate && (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="primary"
|
||||
className="ml-auto w-40"
|
||||
variant={isDirty ? "solid" : "outline_bg"}
|
||||
leftIcon={<FontAwesomeIcon icon={faSave} />}
|
||||
isDisabled={!isAllowed || !isDirty}
|
||||
>
|
||||
{isDirty ? "Save" : "No"} Changes
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
{errors.environments && (
|
||||
<span className="my-4 text-sm text-red">{errors.environments.message}</span>
|
||||
)}
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Friendly Name</Th>
|
||||
<Th>Slug</Th>
|
||||
{!isInfisicalTemplate && (
|
||||
<Th>
|
||||
<div className="flex w-full justify-end normal-case">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
renderTooltip={isEnvironmentLimitExceeded ? true : undefined}
|
||||
allowedLabel={`Plan environment limit of ${subscription.environmentLimit} exceeded. Contact Infisical to increase limit.`}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={() => append({ name: "", slug: "" })}
|
||||
colorSchema="secondary"
|
||||
className="ml-auto"
|
||||
variant="solid"
|
||||
size="xs"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
isDisabled={!isAllowed || isEnvironmentLimitExceeded}
|
||||
>
|
||||
Add Environment
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
</Th>
|
||||
)}
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{environments.map(({ id, name, slug }, pos) => (
|
||||
<Tr key={id}>
|
||||
<Td>
|
||||
{isInfisicalTemplate ? (
|
||||
name
|
||||
) : (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name={`environments.${pos}.name`}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
className="mb-0 flex-grow"
|
||||
>
|
||||
<Input isDisabled={!isAllowed} {...field} placeholder="Name..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{isInfisicalTemplate ? (
|
||||
slug
|
||||
) : (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name={`environments.${pos}.slug`}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
className="mb-0 flex-grow"
|
||||
>
|
||||
<Input isDisabled={!isAllowed} {...field} placeholder="Slug..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</Td>
|
||||
{!isInfisicalTemplate && (
|
||||
<Td className="flex items-center justify-end">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
className={`mr-3 py-2 ${
|
||||
pos === environments.length - 1 ? "pointer-events-none opacity-50" : ""
|
||||
}`}
|
||||
onClick={() => move(pos, pos + 1)}
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="Increase position"
|
||||
isDisabled={pos === environments.length - 1 || !isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowDown} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
className={`mr-3 py-2 ${
|
||||
pos === 0 ? "pointer-events-none opacity-50" : ""
|
||||
}`}
|
||||
onClick={() => move(pos, pos - 1)}
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="Decrease position"
|
||||
isDisabled={pos === 0 || !isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowUp} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
onClick={() => remove(pos)}
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="Remove environment"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
import { faPlus, faTrash, faUnlock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { isCustomProjectRole } from "@app/helpers/roles";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||
|
||||
import { ProjectTemplateEditRoleForm } from "./ProjectTemplateEditRoleForm";
|
||||
|
||||
type Props = {
|
||||
projectTemplate: TProjectTemplate;
|
||||
isInfisicalTemplate: boolean;
|
||||
};
|
||||
|
||||
export const ProjectTemplateRolesSection = ({ projectTemplate, isInfisicalTemplate }: Props) => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"removeRole",
|
||||
"editRole"
|
||||
] as const);
|
||||
|
||||
const { permission } = useOrgPermission();
|
||||
|
||||
const { roles } = projectTemplate;
|
||||
|
||||
const updateProjectTemplate = useUpdateProjectTemplate();
|
||||
|
||||
const handleRemoveRole = async (slug: string) => {
|
||||
try {
|
||||
await updateProjectTemplate.mutateAsync({
|
||||
templateId: projectTemplate.id,
|
||||
roles: projectTemplate.roles.filter(
|
||||
(role) => role.slug !== slug && isCustomProjectRole(role.slug) // filter out default roles as well
|
||||
)
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed role from template",
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpClose("removeRole");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
createNotification({
|
||||
text: "Error removing role from template",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const editRole = popUp?.editRole?.data as TProjectRole;
|
||||
const roleToDelete = popUp?.removeRole?.data as TProjectRole;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<AnimatePresence>
|
||||
{popUp?.editRole.isOpen ? (
|
||||
<motion.div
|
||||
key="edit-role"
|
||||
transition={{ duration: 0.3 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
className="absolute min-h-[10rem] w-full"
|
||||
>
|
||||
<ProjectTemplateEditRoleForm
|
||||
onGoBack={() => handlePopUpClose("editRole")}
|
||||
projectTemplate={projectTemplate}
|
||||
role={editRole}
|
||||
isDisabled={
|
||||
permission.cannot(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.ProjectTemplates
|
||||
) ||
|
||||
(editRole && !isCustomProjectRole(editRole.slug))
|
||||
}
|
||||
/>
|
||||
<div className="h-4 w-full" />
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="role-list"
|
||||
transition={{ duration: 0.3 }}
|
||||
initial={{ opacity: 0, translateX: 0 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
className="absolute w-full"
|
||||
>
|
||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Project Roles</h2>
|
||||
<p className="text-sm text-mineshaft-400">
|
||||
{isInfisicalTemplate
|
||||
? "Click a role to view the associated permissions"
|
||||
: "Add, edit and remove roles for this project template"}
|
||||
</p>
|
||||
</div>
|
||||
{!isInfisicalTemplate && (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
handlePopUpOpen("editRole");
|
||||
}}
|
||||
colorSchema="primary"
|
||||
className="ml-auto"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Role
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Slug</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{roles.length ? (
|
||||
roles.map((role) => {
|
||||
return (
|
||||
<Tr
|
||||
key={role.slug}
|
||||
className="group w-full cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
handlePopUpOpen("editRole", role);
|
||||
}
|
||||
}}
|
||||
onClick={() => handlePopUpOpen("editRole", role)}
|
||||
>
|
||||
<Td>{role.name}</Td>
|
||||
<Td>{role.slug}</Td>
|
||||
<Td>
|
||||
{isCustomProjectRole(role.slug) && (
|
||||
<div className="flex space-x-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
renderTooltip
|
||||
allowedLabel="Remove Role"
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
colorSchema="danger"
|
||||
ariaLabel="delete-icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
isDisabled={!isAllowed}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handlePopUpOpen("removeRole", role);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Tr>
|
||||
<Td colSpan={2}>
|
||||
<EmptyState title="No roles assigned to template" icon={faUnlock} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.removeRole.isOpen}
|
||||
deleteKey="remove"
|
||||
title={`Are you sure you want to remove the role ${roleToDelete?.slug}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("removeRole", isOpen)}
|
||||
onDeleteApproved={() => handleRemoveRole(roleToDelete?.slug)}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-4 w-full" />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./EditProjectTemplate";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./EditProjectTemplateSection";
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
TextArea
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
TProjectTemplate,
|
||||
useCreateProjectTemplate,
|
||||
useUpdateProjectTemplate
|
||||
} from "@app/hooks/api/projectTemplates";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
const formSchema = z.object({
|
||||
name: slugSchema({ min: 1, max: 64, field: "Name" }),
|
||||
description: z.string().max(500).optional()
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
onComplete?: (template: TProjectTemplate) => void;
|
||||
projectTemplate?: TProjectTemplate;
|
||||
};
|
||||
|
||||
type FormProps = {
|
||||
projectTemplate?: TProjectTemplate;
|
||||
onComplete: (template: TProjectTemplate) => void;
|
||||
};
|
||||
|
||||
const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
|
||||
const createProjectTemplate = useCreateProjectTemplate();
|
||||
const updateProjectTemplate = useUpdateProjectTemplate();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { isSubmitting, errors }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: projectTemplate?.name,
|
||||
description: projectTemplate?.description
|
||||
}
|
||||
});
|
||||
|
||||
const onFormSubmit = async (data: FormData) => {
|
||||
const mutation = projectTemplate
|
||||
? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data })
|
||||
: createProjectTemplate.mutateAsync({ ...data });
|
||||
|
||||
try {
|
||||
const template = await mutation;
|
||||
createNotification({
|
||||
text: `Successfully ${
|
||||
projectTemplate ? "updated template details" : "created project template"
|
||||
}`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
onComplete(template);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to ${
|
||||
projectTemplate ? "update template details" : "create project template"
|
||||
}`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<FormControl
|
||||
helperText="Name must be slug-friendly"
|
||||
errorText={errors.name?.message}
|
||||
isError={Boolean(errors.name?.message)}
|
||||
label="Name"
|
||||
>
|
||||
<Input autoFocus placeholder="my-project-template" {...register("name")} />
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Description (optional)"
|
||||
errorText={errors.description?.message}
|
||||
isError={Boolean(errors.description?.message)}
|
||||
>
|
||||
<TextArea
|
||||
className="max-h-[20rem] min-h-[10rem] min-w-full max-w-full"
|
||||
{...register("description")}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{projectTemplate ? "Update" : "Add"} Template
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProjectTemplateDetailsModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
projectTemplate,
|
||||
onComplete
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title={projectTemplate ? "Edit Project Template Details" : "Create Project Template"}
|
||||
>
|
||||
<ProjectTemplateForm
|
||||
projectTemplate={projectTemplate}
|
||||
onComplete={(template) => {
|
||||
if (onComplete) onComplete(template);
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import { useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { TProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { EditProjectTemplateSection } from "./EditProjectTemplateSection";
|
||||
import { ProjectTemplateDetailsModal } from "./ProjectTemplateDetailsModal";
|
||||
import { ProjectTemplatesTable } from "./ProjectTemplatesTable";
|
||||
|
||||
export const ProjectTemplatesSection = () => {
|
||||
const { subscription } = useSubscription();
|
||||
const [editTemplate, setEditTemplate] = useState<TProjectTemplate | null>(null);
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"upgradePlan",
|
||||
"addTemplate"
|
||||
] as const);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<AnimatePresence>
|
||||
{editTemplate ? (
|
||||
<motion.div
|
||||
key="edit-project-template"
|
||||
transition={{ duration: 0.3 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
className="absolute min-h-[10rem] w-full"
|
||||
>
|
||||
<EditProjectTemplateSection
|
||||
template={editTemplate}
|
||||
onBack={() => setEditTemplate(null)}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="project-templates-list"
|
||||
transition={{ duration: 0.3 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
className="absolute min-h-[10rem] w-full"
|
||||
>
|
||||
<div>
|
||||
<p className="mb-6 font-inter text-bunker-300">
|
||||
Create and configure templates with predefined roles and environments to streamline
|
||||
project setup
|
||||
</p>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-start">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Project Templates</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://infisical.com/docs/documentation/platform/project-templates"
|
||||
>
|
||||
<div className="ml-2 mt-[0.32rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
<span>Docs</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.07rem] ml-1.5 text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Create}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (!subscription?.projectTemplates) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("addTemplate");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
className="ml-auto"
|
||||
>
|
||||
Add Template
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<ProjectTemplatesTable onEdit={setEditTemplate} />
|
||||
<ProjectTemplateDetailsModal
|
||||
onComplete={(template) => setEditTemplate(template)}
|
||||
isOpen={popUp.addTemplate.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addTemplate", isOpen)}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can create project templates if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
faCircleInfo,
|
||||
faClone,
|
||||
faMagnifyingGlass,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Input,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
||||
|
||||
import { DeleteProjectTemplateModal } from "./DeleteProjectTemplateModal";
|
||||
|
||||
type Props = {
|
||||
onEdit: (projectTemplate: TProjectTemplate) => void;
|
||||
};
|
||||
|
||||
export const ProjectTemplatesTable = ({ onEdit }: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { isPending, data: projectTemplates = [] } = useListProjectTemplates({
|
||||
enabled: subscription?.projectTemplates
|
||||
});
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteTemplate"] as const);
|
||||
|
||||
const filteredTemplates = useMemo(
|
||||
() =>
|
||||
projectTemplates?.filter((template) =>
|
||||
template.name.toLowerCase().includes(search.toLowerCase().trim())
|
||||
) ?? [],
|
||||
[search, projectTemplates]
|
||||
);
|
||||
|
||||
const colSpan = 4;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search templates..."
|
||||
/>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Roles</Th>
|
||||
<Th>Environments</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{subscription?.projectTemplates && isPending && (
|
||||
<TableSkeleton
|
||||
innerKey="project-templates-table"
|
||||
columns={colSpan}
|
||||
key="project-templates"
|
||||
/>
|
||||
)}
|
||||
{filteredTemplates.map((template) => {
|
||||
const { id, name, roles, environments = [], description } = template;
|
||||
return (
|
||||
<Tr
|
||||
onClick={() => onEdit(template)}
|
||||
className="cursor-pointer hover:bg-mineshaft-700"
|
||||
key={id}
|
||||
>
|
||||
<Td>
|
||||
{name}
|
||||
{description && (
|
||||
<Tooltip content={description}>
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="ml-2 text-mineshaft-400"
|
||||
icon={faCircleInfo}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Td>
|
||||
<Td className="pl-8">
|
||||
{roles.length}
|
||||
{roles.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul className="ml-2 list-disc">
|
||||
{roles.map((role) => (
|
||||
<li key={role.name}>{role.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="ml-2 text-mineshaft-400"
|
||||
icon={faCircleInfo}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Td>
|
||||
<Td className="pl-14">
|
||||
{environments?.length || 0}
|
||||
{environments?.length && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul className="ml-2 list-disc">
|
||||
{environments
|
||||
?.sort((a, b) => (a.position > b.position ? 1 : -1))
|
||||
.map((env) => <li key={env.slug}>{env.name}</li>)}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="ml-2 text-mineshaft-400"
|
||||
icon={faCircleInfo}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Td>
|
||||
<Td className="w-5">
|
||||
{name !== "default" && (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("deleteTemplate", template);
|
||||
}}
|
||||
variant="plain"
|
||||
colorSchema="danger"
|
||||
ariaLabel="Delete template"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
{(!subscription?.projectTemplates ||
|
||||
(!isPending && filteredTemplates?.length === 0)) && (
|
||||
<Tr>
|
||||
<Td colSpan={colSpan}>
|
||||
<EmptyState
|
||||
title={
|
||||
search.trim()
|
||||
? "No project templates match search"
|
||||
: "No project templates found"
|
||||
}
|
||||
icon={faClone}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<DeleteProjectTemplateModal
|
||||
isOpen={popUp.deleteTemplate.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("deleteTemplate", isOpen)}
|
||||
template={popUp.deleteTemplate.data}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { ProjectTemplatesSection } from "./ProjectTemplatesSection";
|
||||
@@ -0,0 +1 @@
|
||||
export { ProjectTemplatesTab } from "./ProjectTemplatesTab";
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -20,11 +18,6 @@ export const Route = createFileRoute(
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverviewPage } from "../SecretManagerOverviewPage/SecretManagerOverviewPage";
|
||||
|
||||
export const SshOverviewPage = () => <ProductOverviewPage type={ProjectType.SSH} />;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { SshOverviewPage } from "./SshOverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview"
|
||||
)({
|
||||
component: SshOverviewPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "SSH",
|
||||
link: linkOptions({ to: "/organization/ssh/overview" })
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
export const SshSettingsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>SSH Settings</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="SSH Settings" />
|
||||
<ProjectSettings />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { SshSettingsPage } from "./SshSettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings"
|
||||
)({
|
||||
component: SshSettingsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Products",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||
},
|
||||
{
|
||||
label: "SSH",
|
||||
link: linkOptions({ to: "/organization/ssh/overview" })
|
||||
},
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -1,6 +1,4 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -20,11 +18,6 @@ export const Route = createFileRoute(
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: "Single Sign-On (SSO)"
|
||||
}
|
||||
|
||||
+2
-5
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { formatProjectRoleName } from "@app/helpers/roles";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
@@ -43,7 +43,7 @@ export const UserProjectRow = ({
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
navigate({
|
||||
to: `/${project.type}/$projectId/access-management` as const,
|
||||
to: "/projects/$projectId/access-management",
|
||||
params: {
|
||||
projectId: project.id
|
||||
},
|
||||
@@ -61,9 +61,6 @@ export const UserProjectRow = ({
|
||||
}}
|
||||
>
|
||||
<Td className="max-w-0 truncate">{project.name}</Td>
|
||||
<Td>
|
||||
<Tag size="xs">{project.type}</Tag>
|
||||
</Td>
|
||||
<Td>{`${formatProjectRoleName(roles[0].role, roles[0].customRoleName)}${
|
||||
roles.length > 1 ? ` (+${roles.length - 1})` : ""
|
||||
}`}</Td>
|
||||
|
||||
-1
@@ -123,7 +123,6 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => {
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>Type</Th>
|
||||
<Th>Role</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
|
||||
@@ -10,11 +8,6 @@ export const Route = createFileRoute(
|
||||
component: UserDetailsByIDPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({ to: "/organization/access-management" })
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
|
||||
import {
|
||||
@@ -25,7 +24,7 @@ const Page = () => {
|
||||
|
||||
const updateSelectedTab = (tab: string) => {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
to: "/projects/$projectId/access-management",
|
||||
search: (prev) => ({ ...prev, selectedTab: tab }),
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
@@ -49,9 +48,7 @@ const Page = () => {
|
||||
<p>Machine Identities</p>
|
||||
</div>
|
||||
</Tab>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab>
|
||||
)}
|
||||
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Roles}>Project Roles</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={ProjectAccessControlTabs.Member}>
|
||||
@@ -63,11 +60,9 @@ const Page = () => {
|
||||
<TabPanel value={ProjectAccessControlTabs.Identities}>
|
||||
<IdentityTab />
|
||||
</TabPanel>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<TabPanel value={ProjectAccessControlTabs.ServiceTokens}>
|
||||
<ServiceTokenTab />
|
||||
</TabPanel>
|
||||
)}
|
||||
<TabPanel value={ProjectAccessControlTabs.ServiceTokens}>
|
||||
<ServiceTokenTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={ProjectAccessControlTabs.Roles}>
|
||||
<ProjectRoleListTab />
|
||||
</TabPanel>
|
||||
|
||||
+2
-2
@@ -158,7 +158,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/groups/$groupId` as const,
|
||||
to: "/projects/$projectId/groups/$groupId",
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
groupId: id
|
||||
@@ -168,7 +168,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
|
||||
}}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/groups/$groupId` as const,
|
||||
to: "/projects/$projectId/groups/$groupId",
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
groupId: id
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user