mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 04:27:38 +00:00
UI improvements on PKI product
This commit is contained in:
@@ -13,6 +13,7 @@ export {
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionPkiSyncActions,
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub
|
||||
|
||||
@@ -24,6 +24,7 @@ export {
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionPkiSyncActions,
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub,
|
||||
|
||||
+5
-5
@@ -72,8 +72,8 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map((ca) => {
|
||||
const canReadCa = permission.can(
|
||||
ProjectPermissionCertificateAuthorityActions.Read,
|
||||
const canEditCa = permission.can(
|
||||
ProjectPermissionCertificateAuthorityActions.Edit,
|
||||
subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||
name: ca.name
|
||||
})
|
||||
@@ -83,12 +83,12 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
<Tr
|
||||
className={twMerge(
|
||||
"h-10 transition-colors duration-100",
|
||||
canReadCa && "cursor-pointer hover:bg-mineshaft-700",
|
||||
!canReadCa && "cursor-not-allowed opacity-60"
|
||||
canEditCa && "cursor-pointer hover:bg-mineshaft-700",
|
||||
!canEditCa && "cursor-not-allowed opacity-60"
|
||||
)}
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() =>
|
||||
canReadCa &&
|
||||
canEditCa &&
|
||||
handlePopUpOpen("ca", {
|
||||
caId: ca.id,
|
||||
name: ca.name,
|
||||
|
||||
+17
-9
@@ -6,6 +6,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCertificateProfileActions,
|
||||
ProjectPermissionSub,
|
||||
useProject
|
||||
} from "@app/context";
|
||||
@@ -104,12 +105,12 @@ export const CertificatesSection = () => {
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-medium text-mineshaft-100">Certificates</p>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionCertificateActions.Create}
|
||||
a={ProjectPermissionSub.Certificates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionCertificateActions.Import}
|
||||
a={ProjectPermissionSub.Certificates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faArrowRight} />}
|
||||
@@ -118,6 +119,13 @@ export const CertificatesSection = () => {
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionCertificateProfileActions.IssueCert}
|
||||
a={ProjectPermissionSub.CertificateProfiles}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
@@ -127,9 +135,9 @@ export const CertificatesSection = () => {
|
||||
>
|
||||
Request
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
</div>
|
||||
<CertificatesTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
|
||||
@@ -41,8 +41,10 @@ import {
|
||||
import { Badge } from "@app/components/v3";
|
||||
import {
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionPkiSyncActions,
|
||||
ProjectPermissionSub,
|
||||
useProject
|
||||
useProject,
|
||||
useProjectPermission
|
||||
} from "@app/context";
|
||||
import { useUpdateRenewalConfig } from "@app/hooks/api";
|
||||
import { caSupportsCapability } from "@app/hooks/api/ca/constants";
|
||||
@@ -97,6 +99,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||
|
||||
const { currentProject } = useProject();
|
||||
const { permission } = useProjectPermission();
|
||||
const { data, isPending } = useListWorkspaceCertificates({
|
||||
projectId: currentProject?.id ?? "",
|
||||
offset: (page - 1) * perPage,
|
||||
@@ -208,55 +211,72 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
return "opacity-0 group-hover:opacity-100";
|
||||
})()}`}
|
||||
>
|
||||
{canShowAutoRenewalIcon && (
|
||||
<Tooltip
|
||||
content={(() => {
|
||||
if (hasFailed && certificate.renewalError) {
|
||||
return `Auto-renewal failed: ${certificate.renewalError}`;
|
||||
}
|
||||
if (isAutoRenewalEnabled) {
|
||||
const expiryDate = new Date(certificate.notAfter);
|
||||
const now = new Date();
|
||||
const daysUntilExpiry = Math.ceil(
|
||||
(expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)
|
||||
);
|
||||
const daysUntilRenewal = Math.max(
|
||||
0,
|
||||
daysUntilExpiry - (certificate.renewBeforeDays || 0)
|
||||
);
|
||||
return `Auto-renews in ${daysUntilRenewal}d`;
|
||||
}
|
||||
return "Set auto renewal";
|
||||
})()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={(() => {
|
||||
if (hasFailed) return "pr-1 text-red-500 hover:text-red-400";
|
||||
return "pr-1 text-primary-500 hover:text-primary-400";
|
||||
})()}
|
||||
aria-label="Certificate auto-renewal"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (hasFailed) return;
|
||||
{canShowAutoRenewalIcon &&
|
||||
(() => {
|
||||
const canEditCertificate = permission.can(
|
||||
ProjectPermissionCertificateActions.Edit,
|
||||
subject(ProjectPermissionSub.Certificates, {
|
||||
commonName: certificate.commonName,
|
||||
altNames: certificate.altNames,
|
||||
serialNumber: certificate.serialNumber
|
||||
})
|
||||
);
|
||||
|
||||
handlePopUpOpen("manageRenewal", {
|
||||
certificateId: certificate.id,
|
||||
commonName: originalDisplayName,
|
||||
profileId: certificate.profileId || "",
|
||||
renewBeforeDays: certificate.renewBeforeDays || 7,
|
||||
ttlDays: Math.ceil(
|
||||
(new Date(certificate.notAfter).getTime() -
|
||||
new Date(certificate.notBefore).getTime()) /
|
||||
(24 * 60 * 60 * 1000)
|
||||
)
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClockRotateLeft} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
return (
|
||||
<Tooltip
|
||||
content={(() => {
|
||||
if (hasFailed && certificate.renewalError) {
|
||||
return `Auto-renewal failed: ${certificate.renewalError}`;
|
||||
}
|
||||
if (isAutoRenewalEnabled) {
|
||||
const expiryDate = new Date(certificate.notAfter);
|
||||
const now = new Date();
|
||||
const daysUntilExpiry = Math.ceil(
|
||||
(expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)
|
||||
);
|
||||
const daysUntilRenewal = Math.max(
|
||||
0,
|
||||
daysUntilExpiry - (certificate.renewBeforeDays || 0)
|
||||
);
|
||||
return `Auto-renews in ${daysUntilRenewal}d`;
|
||||
}
|
||||
return "Set auto renewal";
|
||||
})()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={(() => {
|
||||
if (hasFailed) return "pr-1 text-red-500 hover:text-red-400";
|
||||
return "pr-1 text-primary-500 hover:text-primary-400";
|
||||
})()}
|
||||
aria-label="Certificate auto-renewal"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!canEditCertificate) return;
|
||||
if (hasFailed) return;
|
||||
|
||||
handlePopUpOpen("manageRenewal", {
|
||||
certificateId: certificate.id,
|
||||
commonName: originalDisplayName,
|
||||
profileId: certificate.profileId || "",
|
||||
renewBeforeDays: certificate.renewBeforeDays || 7,
|
||||
ttlDays: Math.ceil(
|
||||
(new Date(certificate.notAfter).getTime() -
|
||||
new Date(certificate.notBefore).getTime()) /
|
||||
(24 * 60 * 60 * 1000)
|
||||
),
|
||||
notAfter: certificate.notAfter,
|
||||
renewalError: certificate.renewalError,
|
||||
renewedFromCertificateId: certificate.renewedFromCertificateId,
|
||||
renewedByCertificateId: certificate.renewedByCertificateId
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClockRotateLeft} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
@@ -481,13 +501,8 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
{certificate.status === CertStatus.ACTIVE &&
|
||||
!certificate.renewedByCertificateId && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionCertificateActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Certificates, {
|
||||
commonName: certificate.commonName,
|
||||
altNames: certificate.altNames,
|
||||
serialNumber: certificate.serialNumber,
|
||||
friendlyName: certificate.friendlyName
|
||||
})}
|
||||
I={ProjectPermissionPkiSyncActions.Edit}
|
||||
a={ProjectPermissionSub.PkiSyncs}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
@@ -509,14 +524,14 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
{/* Only show revoke button if CA supports revocation */}
|
||||
{/* Only show revoke button if CA supports revocation and certificate is not already revoked */}
|
||||
{(() => {
|
||||
const caType = caCapabilityMap[certificate.caId];
|
||||
const supportsRevocation =
|
||||
!caType ||
|
||||
caSupportsCapability(caType, CaCapability.REVOKE_CERTIFICATES);
|
||||
|
||||
if (!supportsRevocation) {
|
||||
if (!supportsRevocation || isRevoked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,15 @@ import { useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||
import { ContentLoader, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { useProject } from "@app/context";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
import {
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCertificateProfileActions,
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { CertificateProfilesTab } from "./components/CertificateProfilesTab";
|
||||
@@ -20,8 +27,22 @@ enum TabSections {
|
||||
export const PoliciesPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentProject } = useProject();
|
||||
const { permission } = useProjectPermission();
|
||||
const [activeTab, setActiveTab] = useState(TabSections.CertificateProfiles);
|
||||
|
||||
const canReadCertificateProfiles = permission.can(
|
||||
ProjectPermissionCertificateProfileActions.Read,
|
||||
ProjectPermissionSub.CertificateProfiles
|
||||
);
|
||||
const canReadCertificateTemplates = permission.can(
|
||||
ProjectPermissionPkiTemplateActions.Read,
|
||||
ProjectPermissionSub.CertificateTemplates
|
||||
);
|
||||
const canReadCertificates = permission.can(
|
||||
ProjectPermissionCertificateActions.Read,
|
||||
ProjectPermissionSub.Certificates
|
||||
);
|
||||
|
||||
if (!currentProject) {
|
||||
return <ContentLoader />;
|
||||
}
|
||||
@@ -56,15 +77,19 @@ export const PoliciesPage = () => {
|
||||
</TabList>
|
||||
|
||||
<TabPanel value={TabSections.CertificateProfiles}>
|
||||
<CertificateProfilesTab />
|
||||
{canReadCertificateProfiles ? <CertificateProfilesTab /> : <PermissionDeniedBanner />}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={TabSections.CertificateTemplatesV2}>
|
||||
<CertificateTemplatesV2Tab />
|
||||
{canReadCertificateTemplates ? (
|
||||
<CertificateTemplatesV2Tab />
|
||||
) : (
|
||||
<PermissionDeniedBanner />
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={TabSections.Certificates}>
|
||||
<CertificatesTab />
|
||||
{canReadCertificates ? <CertificatesTab /> : <PermissionDeniedBanner />}
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
+5
-2
@@ -258,7 +258,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
};
|
||||
};
|
||||
|
||||
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<
|
||||
const { control, handleSubmit, reset, watch, setValue, formState, trigger } = useForm<
|
||||
FormData & { preset: TemplatePresetId }
|
||||
>({
|
||||
resolver: zodResolver(templateSchema),
|
||||
@@ -286,10 +286,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
};
|
||||
const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM;
|
||||
|
||||
const handlePresetChange = (presetId: TemplatePresetId) => {
|
||||
const handlePresetChange = async (presetId: TemplatePresetId) => {
|
||||
setValue("preset", presetId);
|
||||
|
||||
if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) {
|
||||
await trigger();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -313,6 +314,8 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
if (selectedPreset.formData.keyAlgorithm) {
|
||||
setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm);
|
||||
}
|
||||
|
||||
await trigger();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user