mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 08:34:52 +00:00
Add SSH certificate tab + data structure
This commit is contained in:
@@ -34,7 +34,8 @@ export enum OrgPermissionSubjects {
|
||||
AuditLogs = "audit-logs",
|
||||
ProjectTemplates = "project-templates",
|
||||
SshCertificateAuthorities = "ssh-certificate-authorities",
|
||||
SshCertificateTemplates = "ssh-certificate-templates"
|
||||
SshCertificateTemplates = "ssh-certificate-templates",
|
||||
SshCertificates = "ssh-certificates"
|
||||
}
|
||||
|
||||
export enum OrgPermissionAdminConsoleAction {
|
||||
@@ -60,6 +61,7 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificates]
|
||||
| [OrgPermissionSshCertificateTemplateActions, OrgPermissionSubjects.SshCertificateTemplates];
|
||||
|
||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||
|
||||
@@ -20,5 +20,7 @@ export {
|
||||
useGetOrgTaxIds,
|
||||
useGetOrgTrialUrl,
|
||||
useListOrgSshCas,
|
||||
useListOrgSshCertificates,
|
||||
useListOrgSshCertificateTemplates,
|
||||
useUpdateOrg,
|
||||
useUpdateOrgBillingDetails} from "./queries";
|
||||
|
||||
@@ -4,7 +4,8 @@ import { apiRequest } from "@app/config/request";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
|
||||
import { TGroupOrgMembership } from "../groups/types";
|
||||
import { TSshCertificateAuthority } from "../ssh-ca/types";
|
||||
import { TSshCertificate,TSshCertificateAuthority } from "../ssh-ca/types";
|
||||
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
||||
import { IntegrationAuth } from "../types";
|
||||
import {
|
||||
BillingDetails,
|
||||
@@ -43,7 +44,11 @@ export const organizationKeys = {
|
||||
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
|
||||
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
|
||||
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
|
||||
getOrgSshCas: ({ orgId }: { orgId: string }) => [{ orgId }, "org-ssh-cas"] as const
|
||||
getOrgSshCas: ({ orgId }: { orgId: string }) => [{ orgId }, "org-ssh-cas"] as const,
|
||||
allOrgSshCertificates: () => ["org-ssh-certificates"] as const,
|
||||
specificOrgSshCertificates: ({ offset, limit }: { offset: number; limit: number }) =>
|
||||
[...organizationKeys.allOrgSshCertificates(), { offset, limit }] as const,
|
||||
getOrgSshCertificateTemplates: () => ["org-ssh-certificate-templates"] as const
|
||||
};
|
||||
|
||||
export const fetchOrganizations = async () => {
|
||||
@@ -512,3 +517,48 @@ export const useListOrgSshCas = ({ orgId }: { orgId: string }) => {
|
||||
enabled: Boolean(orgId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useListOrgSshCertificates = ({
|
||||
orgId,
|
||||
offset,
|
||||
limit
|
||||
}: {
|
||||
orgId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: organizationKeys.specificOrgSshCertificates({
|
||||
offset,
|
||||
limit
|
||||
}),
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit)
|
||||
});
|
||||
|
||||
const { data } = await apiRequest.get<{
|
||||
certificates: TSshCertificate[];
|
||||
totalCount: number;
|
||||
}>(`/api/v1/organization/${orgId}/ssh-certificates`, {
|
||||
params
|
||||
});
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(orgId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useListOrgSshCertificateTemplates = ({ orgId }: { orgId: string }) => {
|
||||
return useQuery({
|
||||
queryKey: organizationKeys.getOrgSshCertificateTemplates(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ certificateTemplates: TSshCertificateTemplate[] }>(
|
||||
`/api/v1/organization/${orgId}/ssh-certificate-templates`
|
||||
);
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(orgId)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,3 +7,8 @@ export enum SshCertType {
|
||||
USER = "user",
|
||||
HOST = "host"
|
||||
}
|
||||
|
||||
export const sshCertTypeToNameMap: { [K in SshCertType]: string } = {
|
||||
[SshCertType.USER]: "User",
|
||||
[SshCertType.HOST]: "Host"
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
export { SshCaStatus } from "./enums";
|
||||
export { SshCaStatus } from "./constants";
|
||||
export {
|
||||
useCreateSshCa,
|
||||
useDeleteSshCa,
|
||||
useIssueSshCreds,
|
||||
useSignSshKey,
|
||||
useUpdateSshCa} from "./mutations";
|
||||
useUpdateSshCa
|
||||
} from "./mutations";
|
||||
export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries";
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
TSignSshKeyDTO,
|
||||
TSignSshKeyResponse,
|
||||
TSshCertificateAuthority,
|
||||
TUpdateSshCaDTO} from "./types";
|
||||
TUpdateSshCaDTO
|
||||
} from "./types";
|
||||
|
||||
export const sshCaKeys = {
|
||||
getSshCaById: (caId: string) => [{ caId }, "ssh-ca"]
|
||||
@@ -64,19 +65,27 @@ export const useDeleteSshCa = () => {
|
||||
};
|
||||
|
||||
export const useSignSshKey = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TSignSshKeyResponse, {}, TSignSshKeyDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data } = await apiRequest.post<TSignSshKeyResponse>("/api/v1/ssh/sign", body);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(organizationKeys.allOrgSshCertificates());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useIssueSshCreds = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TIssueSshCredsResponse, {}, TIssueSshCredsDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data } = await apiRequest.post<TIssueSshCredsResponse>("/api/v1/ssh/issue", body);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(organizationKeys.allOrgSshCertificates());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { CertKeyAlgorithm } from "../certificates/enums";
|
||||
import { SshCaStatus, SshCertType } from "./enums";
|
||||
import { SshCaStatus, SshCertType } from "./constants";
|
||||
|
||||
export type TSshCertificate = {
|
||||
id: string;
|
||||
sshCaId: string;
|
||||
sshCertificateTemplateId: string;
|
||||
serialNumber: string;
|
||||
certType: SshCertType;
|
||||
publicKey: string;
|
||||
principals: string[];
|
||||
keyId: string;
|
||||
};
|
||||
|
||||
export type TSshCertificateAuthority = {
|
||||
id: string;
|
||||
|
||||
@@ -3,11 +3,8 @@ import Head from "next/head";
|
||||
|
||||
import { SshPage } from "@app/views/Org/SshPage";
|
||||
|
||||
// TODO: update meta tags
|
||||
|
||||
const Ssh = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
||||
@@ -13,6 +13,13 @@ const generalPermissionSchema = z
|
||||
})
|
||||
.optional();
|
||||
|
||||
const sshCertificateSchema = z
|
||||
.object({
|
||||
read: z.boolean().optional(),
|
||||
create: z.boolean().optional()
|
||||
})
|
||||
.optional();
|
||||
|
||||
const sshCertificateTemplatePermissionSchmea = z
|
||||
.object({
|
||||
read: z.boolean().optional(),
|
||||
@@ -62,6 +69,7 @@ export const formSchema = z.object({
|
||||
[OrgPermissionSubjects.Kms]: generalPermissionSchema,
|
||||
[OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema,
|
||||
[OrgPermissionSubjects.SshCertificateAuthorities]: generalPermissionSchema,
|
||||
[OrgPermissionSubjects.SshCertificates]: sshCertificateSchema,
|
||||
"ssh-certificate-templates": sshCertificateTemplatePermissionSchmea
|
||||
})
|
||||
.optional()
|
||||
|
||||
+7
@@ -51,6 +51,11 @@ const PROJECT_TEMPLATES_PERMISSIONS = [
|
||||
{ action: "delete", label: "Remove" }
|
||||
] as const;
|
||||
|
||||
const SSH_CERTIFICATES_PERMISSIONS = [
|
||||
{ action: "read", label: "View" },
|
||||
{ action: "create", label: "Create" }
|
||||
] as const;
|
||||
|
||||
const getPermissionList = (option: string) => {
|
||||
switch (option) {
|
||||
case "secret-scanning":
|
||||
@@ -63,6 +68,8 @@ const getPermissionList = (option: string) => {
|
||||
return MEMBERS_PERMISSIONS;
|
||||
case OrgPermissionSubjects.ProjectTemplates:
|
||||
return PROJECT_TEMPLATES_PERMISSIONS;
|
||||
case OrgPermissionSubjects.SshCertificates:
|
||||
return SSH_CERTIFICATES_PERMISSIONS;
|
||||
default:
|
||||
return PERMISSIONS;
|
||||
}
|
||||
|
||||
+8
-6
@@ -15,7 +15,6 @@ import {
|
||||
import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow";
|
||||
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
|
||||
import { RolePermissionRow } from "./RolePermissionRow";
|
||||
import { SshCertificateTemplateRow } from "./SshCertificateTemplateRow";
|
||||
|
||||
const SIMPLE_PERMISSION_OPTIONS = [
|
||||
{
|
||||
@@ -74,6 +73,14 @@ const SIMPLE_PERMISSION_OPTIONS = [
|
||||
{
|
||||
title: "SSH Certificate Authorities",
|
||||
formName: OrgPermissionSubjects.SshCertificateAuthorities
|
||||
},
|
||||
{
|
||||
title: "SSH Certificates",
|
||||
formName: OrgPermissionSubjects.SshCertificates
|
||||
},
|
||||
{
|
||||
title: "SSH Certificate Templates",
|
||||
formName: OrgPermissionSubjects.SshCertificateTemplates
|
||||
}
|
||||
] as const;
|
||||
|
||||
@@ -169,11 +176,6 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<SshCertificateTemplateRow
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
isEditable={isCustomRole}
|
||||
/>
|
||||
<OrgRoleWorkspaceRow
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { TFormSchema } from "@app/views/Org/RolePage/components/OrgRoleModifySection.utils";
|
||||
|
||||
type Props = {
|
||||
isEditable: boolean;
|
||||
setValue: UseFormSetValue<TFormSchema>;
|
||||
control: Control<TFormSchema>;
|
||||
};
|
||||
|
||||
enum Permission {
|
||||
NoAccess = "no-access",
|
||||
Custom = "custom"
|
||||
}
|
||||
|
||||
const PERMISSION_ACTIONS = [
|
||||
{ action: "read", label: "Read" },
|
||||
{ action: "create", label: "Create" },
|
||||
{ action: "edit", label: "Modify" },
|
||||
{ action: "delete", label: "Remove" },
|
||||
{ action: "sign-ssh-key", label: "Sign SSH Key" },
|
||||
{ action: "issue-ssh-credentials", label: "Issue SSH Credentials" }
|
||||
] as const;
|
||||
|
||||
export const SshCertificateTemplateRow = ({ isEditable, control, setValue }: Props) => {
|
||||
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||
const [isCustom, setIsCustom] = useToggle();
|
||||
|
||||
const rule = useWatch({
|
||||
control,
|
||||
name: "permissions.ssh-certificate-templates"
|
||||
});
|
||||
|
||||
const selectedPermissionCategory = useMemo(() => {
|
||||
if (rule?.create) {
|
||||
return Permission.Custom;
|
||||
}
|
||||
return Permission.NoAccess;
|
||||
}, [rule, isCustom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||
else setIsCustom.off();
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
const isRowCustom = selectedPermissionCategory === Permission.Custom;
|
||||
if (isRowCustom) {
|
||||
setIsRowExpanded.on();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if (!val) return;
|
||||
if (val === Permission.Custom) {
|
||||
setIsRowExpanded.on();
|
||||
setIsCustom.on();
|
||||
return;
|
||||
}
|
||||
setIsCustom.off();
|
||||
|
||||
if (val === Permission.NoAccess) {
|
||||
setValue("permissions.workspace", { create: false }, { shouldDirty: true });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>SSH Certificate Templates</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
</Select>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && " border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`permissions.${OrgPermissionSubjects.SshCertificateTemplates}.${action}`}
|
||||
key={`permissions.${OrgPermissionSubjects.SshCertificateTemplates}.${action}`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={(e) => {
|
||||
if (!isEditable) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to update default role"
|
||||
});
|
||||
return;
|
||||
}
|
||||
field.onChange(e);
|
||||
}}
|
||||
id={`permissions.${OrgPermissionSubjects.SshCertificateTemplates}.${action}`}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -164,7 +164,7 @@ export const SshCertificateContent = ({
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<div className="flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 whitespace-pre-wrap break-all">{publicKey}</p>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { useGetSshCaCertTemplates, useIssueSshCreds, useSignSshKey } from "@app/hooks/api";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useIssueSshCreds, useListOrgSshCertificateTemplates, useSignSshKey } from "@app/hooks/api";
|
||||
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
||||
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
||||
import { SshCertType } from "@app/hooks/api/ssh-ca/enums";
|
||||
@@ -62,6 +63,7 @@ enum SshCertificateOperation {
|
||||
}
|
||||
|
||||
export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const [operation, setOperation] = useState<SshCertificateOperation>(
|
||||
SshCertificateOperation.SIGN_SSH_KEY
|
||||
);
|
||||
@@ -72,7 +74,9 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
|
||||
const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string };
|
||||
|
||||
const { data: templatesData } = useGetSshCaCertTemplates(popUpData?.sshCaId || "");
|
||||
const { data: templatesData } = useListOrgSshCertificateTemplates({
|
||||
orgId: currentOrg?.id || ""
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -91,6 +95,8 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
useEffect(() => {
|
||||
if (popUpData) {
|
||||
setValue("templateName", popUpData.templateName);
|
||||
} else if (templatesData && templatesData.certificateTemplates.length > 0) {
|
||||
setValue("templateName", templatesData.certificateTemplates[0].name);
|
||||
}
|
||||
}, [popUpData]);
|
||||
|
||||
@@ -114,6 +120,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
ttl,
|
||||
keyId
|
||||
});
|
||||
|
||||
setCertificateDetails({
|
||||
serialNumber,
|
||||
signedKey
|
||||
@@ -186,7 +193,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
isDisabled
|
||||
isDisabled={Boolean(popUpData?.sshCaId)}
|
||||
>
|
||||
{(templatesData?.certificateTemplates || []).map(({ id, name }) => (
|
||||
<SelectItem value={name} key={`ssh-cert-template-${id}`}>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
import { SshCaSection } from "./components";
|
||||
import { SshCaSection, SshCertificatesSection } from "./components";
|
||||
|
||||
enum TabSections {
|
||||
SshCa = "ssh-certificate-authorities",
|
||||
SshCertificates = "ssh-certificates"
|
||||
}
|
||||
|
||||
export const SshPage = withPermission(
|
||||
() => {
|
||||
@@ -9,7 +17,34 @@ export const SshPage = withPermission(
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||
<p className="mr-4 mb-4 text-3xl font-semibold text-white">SSH</p>
|
||||
<SshCaSection />
|
||||
<Tabs defaultValue={TabSections.SshCertificates}>
|
||||
<TabList>
|
||||
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
|
||||
<Tab value={TabSections.SshCa}>Certificate Authorities</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.SshCertificates}>
|
||||
<motion.div
|
||||
key="panel-ssh-certificate-s"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<SshCertificatesSection />
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.SshCa}>
|
||||
<motion.div
|
||||
key="panel-ssh-certificate-authorities"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<SshCaSection />
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { SshCertificateModal } from "../../SshCaPage/components/SshCertificateModal";
|
||||
import { SshCertificatesTable } from "./SshCertificatesTable";
|
||||
|
||||
export const SshCertificatesSection = () => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
|
||||
return (
|
||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.SshCertificates}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("sshCertificate")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Request
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<SshCertificatesTable />
|
||||
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useState } from "react";
|
||||
import { faCertificate } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
Pagination,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr} from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useListOrgSshCertificates } from "@app/hooks/api";
|
||||
import { sshCertTypeToNameMap } from "@app/hooks/api/ssh-ca/constants";
|
||||
|
||||
const PER_PAGE_INIT = 25;
|
||||
|
||||
export const SshCertificatesTable = () => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||
|
||||
const { data, isLoading } = useListOrgSshCertificates({
|
||||
orgId: currentOrg?.id ?? "",
|
||||
offset: (page - 1) * perPage,
|
||||
limit: perPage
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Serial Number</Th>
|
||||
<Th>Certificate Type</Th>
|
||||
<Th>Principals</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="org-ssh-certificates" />}
|
||||
{!isLoading &&
|
||||
data?.certificates?.map((certificate) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
||||
<Td>{certificate.serialNumber}</Td>
|
||||
<Td>{sshCertTypeToNameMap[certificate.certType]}</Td>
|
||||
<Td>{certificate.principals.join(", ")}</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isLoading && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
|
||||
<Pagination
|
||||
count={data.totalCount}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={(newPage) => setPage(newPage)}
|
||||
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && !data?.certificates?.length && (
|
||||
<EmptyState title="No SSH certificates have been issued" icon={faCertificate} />
|
||||
)}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export { SshCaSection } from "./SshCaSection";
|
||||
export { SshCertificatesSection } from "./SshCertificatesSection";
|
||||
|
||||
Reference in New Issue
Block a user