mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 01:29:26 +00:00
237 lines
7.0 KiB
TypeScript
237 lines
7.0 KiB
TypeScript
import { useCallback } from "react";
|
|
import {
|
|
faCheck,
|
|
faCircleInfo,
|
|
faCopy,
|
|
faEdit,
|
|
faEllipsis,
|
|
faPlus,
|
|
faTrash
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
|
|
import { createNotification } from "@app/components/notifications";
|
|
import {
|
|
Badge,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
Td,
|
|
Tooltip,
|
|
Tr
|
|
} from "@app/components/v2";
|
|
import { useProjectPermission } from "@app/context";
|
|
import {
|
|
ProjectPermissionActions,
|
|
ProjectPermissionCertificateProfileActions,
|
|
ProjectPermissionSub
|
|
} from "@app/context/ProjectPermissionContext/types";
|
|
import { usePopUp, useToggle } from "@app/hooks";
|
|
import { useGetCaById } from "@app/hooks/api/ca/queries";
|
|
import { TCertificateProfile } from "@app/hooks/api/certificateProfiles";
|
|
import { useGetCertificateTemplateV2ById } from "@app/hooks/api/certificateTemplates/queries";
|
|
import { CertificateIssuanceModal } from "@app/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal";
|
|
|
|
const MetricsBadges = ({
|
|
metrics
|
|
}: {
|
|
metrics?: {
|
|
totalCertificates: number;
|
|
activeCertificates: number;
|
|
expiringCertificates: number;
|
|
expiredCertificates: number;
|
|
revokedCertificates: number;
|
|
};
|
|
}) => {
|
|
if (!metrics) {
|
|
return (
|
|
<Badge variant="primary" className="text-xs">
|
|
No metrics
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
if (metrics.totalCertificates === 0) {
|
|
return (
|
|
<Badge variant="primary" className="text-xs">
|
|
No certificates
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{metrics.activeCertificates > 0 && (
|
|
<Badge variant="success" className="text-xs">
|
|
{metrics.activeCertificates} active
|
|
</Badge>
|
|
)}
|
|
{metrics.expiringCertificates > 0 && (
|
|
<Badge variant="primary" className="text-xs">
|
|
{metrics.expiringCertificates} expiring
|
|
</Badge>
|
|
)}
|
|
{metrics.expiredCertificates > 0 && (
|
|
<Badge variant="danger" className="text-xs">
|
|
{metrics.expiredCertificates} expired
|
|
</Badge>
|
|
)}
|
|
{metrics.revokedCertificates > 0 && (
|
|
<Badge variant="danger" className="text-xs">
|
|
{metrics.revokedCertificates} revoked
|
|
</Badge>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
interface Props {
|
|
profile: TCertificateProfile;
|
|
onEditProfile: (profile: TCertificateProfile) => void;
|
|
onDeleteProfile: (profile: TCertificateProfile) => void;
|
|
}
|
|
|
|
export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) => {
|
|
const { permission } = useProjectPermission();
|
|
|
|
const { data: caData } = useGetCaById(profile.caId);
|
|
|
|
const { popUp, handlePopUpToggle } = usePopUp(["certificateIssuance"] as const);
|
|
|
|
const [isIdCopied, setIsIdCopied] = useToggle(false);
|
|
|
|
const handleCopyId = useCallback(() => {
|
|
setIsIdCopied.on();
|
|
navigator.clipboard.writeText(profile.id);
|
|
|
|
createNotification({
|
|
text: "Profile ID copied to clipboard",
|
|
type: "info"
|
|
});
|
|
|
|
setTimeout(() => setIsIdCopied.off(), 2000);
|
|
}, [setIsIdCopied]);
|
|
|
|
const { data: templateData } = useGetCertificateTemplateV2ById({
|
|
templateId: profile.certificateTemplateId
|
|
});
|
|
|
|
const canEditProfile = permission.can(
|
|
ProjectPermissionActions.Edit,
|
|
ProjectPermissionSub.CertificateAuthorities
|
|
);
|
|
|
|
const canIssueCertificate = permission.can(
|
|
ProjectPermissionCertificateProfileActions.IssueCert,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const canDeleteProfile = permission.can(
|
|
ProjectPermissionActions.Delete,
|
|
ProjectPermissionSub.CertificateAuthorities
|
|
);
|
|
|
|
const getEnrollmentTypeBadge = (enrollmentType: string) => {
|
|
const config = {
|
|
api: { variant: "success" as const, label: "API" },
|
|
est: { variant: "primary" as const, label: "EST" }
|
|
} as const;
|
|
|
|
const configKey = Object.keys(config).includes(enrollmentType)
|
|
? (enrollmentType as keyof typeof config)
|
|
: "api";
|
|
const { variant, label } = config[configKey];
|
|
|
|
return <Badge variant={variant}>{label}</Badge>;
|
|
};
|
|
|
|
return (
|
|
<Tr key={profile.id} className="h-10 transition-colors duration-100 hover:bg-mineshaft-700">
|
|
<Td>
|
|
<div className="flex items-center gap-2">
|
|
<div className="text-mineshaft-300">{profile.slug}</div>
|
|
{profile.description && (
|
|
<Tooltip content={profile.description}>
|
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
</Td>
|
|
<Td className="text-start">{getEnrollmentTypeBadge(profile.enrollmentType)}</Td>
|
|
<Td className="text-start">
|
|
<span className="text-sm text-mineshaft-300">
|
|
{caData?.friendlyName || caData?.commonName || profile.caId}
|
|
</span>
|
|
</Td>
|
|
<Td>
|
|
<span className="text-sm text-mineshaft-300">
|
|
{templateData?.name || profile.certificateTemplateId}
|
|
</span>
|
|
</Td>
|
|
<Td>
|
|
<div className="flex flex-wrap gap-1">
|
|
<MetricsBadges metrics={profile.metrics} />
|
|
</div>
|
|
</Td>
|
|
<Td className="text-right">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
|
<Tooltip content="More options">
|
|
<FontAwesomeIcon size="lg" icon={faEllipsis} />
|
|
</Tooltip>
|
|
</div>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start" className="p-1">
|
|
<DropdownMenuItem
|
|
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
|
onClick={() => handleCopyId()}
|
|
>
|
|
Copy Profile ID
|
|
</DropdownMenuItem>
|
|
{canEditProfile && (
|
|
<DropdownMenuItem
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onEditProfile(profile);
|
|
}}
|
|
icon={<FontAwesomeIcon icon={faEdit} />}
|
|
>
|
|
Edit Profile
|
|
</DropdownMenuItem>
|
|
)}
|
|
{canIssueCertificate && profile.enrollmentType === "api" && (
|
|
<DropdownMenuItem
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handlePopUpToggle("certificateIssuance");
|
|
}}
|
|
icon={<FontAwesomeIcon icon={faPlus} />}
|
|
>
|
|
Issue Certificate
|
|
</DropdownMenuItem>
|
|
)}
|
|
{canDeleteProfile && (
|
|
<DropdownMenuItem
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onDeleteProfile(profile);
|
|
}}
|
|
icon={<FontAwesomeIcon icon={faTrash} />}
|
|
>
|
|
Delete Profile
|
|
</DropdownMenuItem>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
<CertificateIssuanceModal
|
|
popUp={popUp}
|
|
handlePopUpToggle={handlePopUpToggle}
|
|
profileId={profile.id}
|
|
/>
|
|
</Td>
|
|
</Tr>
|
|
);
|
|
};
|