misc: add indicators for errors

This commit is contained in:
Sheen Capadngan
2025-05-17 23:52:20 +08:00
parent 260679b01d
commit 4d95052896
12 changed files with 102 additions and 25 deletions
@@ -88,6 +88,9 @@ export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.PkiSubscriber)) { if (await knex.schema.hasTable(TableName.PkiSubscriber)) {
await knex.schema.alterTable(TableName.PkiSubscriber, (t) => { await knex.schema.alterTable(TableName.PkiSubscriber, (t) => {
t.string("ttl").nullable().alter(); t.string("ttl").nullable().alter();
t.string("lastOperationStatus");
t.text("lastOperationMessage");
t.string("lastOperationAt");
}); });
} }
} }
@@ -164,4 +167,12 @@ export async function down(knex: Knex): Promise<void> {
if (hasExternalCATable) { if (hasExternalCATable) {
await knex.schema.dropTable(TableName.ExternalCertificateAuthority); await knex.schema.dropTable(TableName.ExternalCertificateAuthority);
} }
if (await knex.schema.hasTable(TableName.PkiSubscriber)) {
await knex.schema.alterTable(TableName.PkiSubscriber, (t) => {
t.dropColumn("lastOperationStatus");
t.dropColumn("lastOperationMessage");
t.dropColumn("lastOperationAt");
});
}
} }
+4 -1
View File
@@ -19,7 +19,10 @@ export const PkiSubscribersSchema = z.object({
ttl: z.string().nullable().optional(), ttl: z.string().nullable().optional(),
keyUsages: z.string().array(), keyUsages: z.string().array(),
extendedKeyUsages: z.string().array(), extendedKeyUsages: z.string().array(),
status: z.string() status: z.string(),
lastOperationStatus: z.string().nullable().optional(),
lastOperationMessage: z.string().nullable().optional(),
lastOperationAt: z.string().nullable().optional()
}); });
export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>; export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>;
@@ -180,7 +180,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
ttl: z ttl: z
.string() .string()
.trim() .trim()
.refine((val) => ms(val) > 0, "TTL must be a positive number") .refine((val) => !val || ms(val) > 0, "TTL must be a positive number")
.optional() .optional()
.describe(PKI_SUBSCRIBERS.UPDATE.ttl), .describe(PKI_SUBSCRIBERS.UPDATE.ttl),
keyUsages: z keyUsages: z
@@ -86,8 +86,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
serialNumber: result.internalSerialNumber, serialNumber: result.internalSerialNumber,
maxPathLength: result.internalMaxPathLength, maxPathLength: result.internalMaxPathLength,
keyAlgorithm: result.internalKeyAlgorithm, keyAlgorithm: result.internalKeyAlgorithm,
notBefore: result.internalNotBefore, notBefore: result.internalNotBefore?.toISOString(),
notAfter: result.internalNotAfter, notAfter: result.internalNotAfter?.toISOString(),
activeCaCertId: result.internalActiveCaCertId, activeCaCertId: result.internalActiveCaCertId,
certificateAuthorityId: result.internalCertificateAuthorityId certificateAuthorityId: result.internalCertificateAuthorityId
} }
@@ -232,8 +232,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
serialNumber: ca.internalSerialNumber, serialNumber: ca.internalSerialNumber,
maxPathLength: ca.internalMaxPathLength, maxPathLength: ca.internalMaxPathLength,
keyAlgorithm: ca.internalKeyAlgorithm, keyAlgorithm: ca.internalKeyAlgorithm,
notBefore: ca.internalNotBefore, notBefore: ca.internalNotBefore?.toISOString(),
notAfter: ca.internalNotAfter, notAfter: ca.internalNotAfter?.toISOString(),
activeCaCertId: ca.internalActiveCaCertId, activeCaCertId: ca.internalActiveCaCertId,
certificateAuthorityId: ca.internalCertificateAuthorityId certificateAuthorityId: ca.internalCertificateAuthorityId
} }
@@ -19,6 +19,7 @@ import { TAppConnectionServiceFactory } from "../app-connection/app-connection-s
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal"; import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal";
import { SubscriberOperationStatus } from "../pki-subscriber/pki-subscriber-types";
import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns"; import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { CaType } from "./certificate-authority-enums"; import { CaType } from "./certificate-authority-enums";
@@ -47,7 +48,7 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">; certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">; certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById">; pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById" | "updateById">;
}; };
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>; export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
@@ -152,8 +153,20 @@ export const certificateAuthorityQueueFactory = ({
try { try {
if (caType === CaType.ACME) { if (caType === CaType.ACME) {
await acmeFns.orderSubscriberCertificate(subscriberId); await acmeFns.orderSubscriberCertificate(subscriberId);
await pkiSubscriberDAL.updateById(subscriberId, {
lastOperationStatus: SubscriberOperationStatus.SUCCESS,
lastOperationMessage: "Certificate ordered successfully",
lastOperationAt: new Date().toISOString()
});
}
} catch (e: unknown) {
if (e instanceof Error) {
await pkiSubscriberDAL.updateById(subscriberId, {
lastOperationStatus: SubscriberOperationStatus.FAILED,
lastOperationMessage: e.message,
lastOperationAt: new Date().toISOString()
});
} }
} catch (e) {
logger.error(e, `CaOrderCertificate Failed [subscriberId=${subscriberId}] [job=${job.name}]`); logger.error(e, `CaOrderCertificate Failed [subscriberId=${subscriberId}] [job=${job.name}]`);
} finally { } finally {
await lock.release(); await lock.release();
@@ -18,7 +18,7 @@ const InternalCertificateAuthorityConfigurationSchema = z
organization: z.string().trim(), organization: z.string().trim(),
ou: z.string().trim(), ou: z.string().trim(),
dn: z.string().trim(), dn: z.string().trim(),
parentCaId: z.string().uuid().optional(), parentCaId: z.string().uuid().nullish(),
serialNumber: z.string().trim().optional(), serialNumber: z.string().trim().optional(),
activeCaCertId: z.string().uuid().optional(), activeCaCertId: z.string().uuid().optional(),
country: z.string().trim(), country: z.string().trim(),
@@ -12,7 +12,10 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
subjectAlternativeNames: true, subjectAlternativeNames: true,
ttl: true, ttl: true,
keyUsages: true, keyUsages: true,
extendedKeyUsages: true extendedKeyUsages: true,
lastOperationStatus: true,
lastOperationMessage: true,
lastOperationAt: true
}).extend({ }).extend({
supportsImmediateCertIssuance: z.boolean().optional() supportsImmediateCertIssuance: z.boolean().optional()
}); });
@@ -56,3 +56,8 @@ export type TListPkiSubscriberCertsDTO = {
offset: number; offset: number;
limit: number; limit: number;
} & TProjectPermission; } & TProjectPermission;
export enum SubscriberOperationStatus {
SUCCESS = "success",
FAILED = "failed"
}
@@ -39,13 +39,16 @@ export const pkiSubscriberKeys = {
] as const ] as const
}; };
export const useGetPkiSubscriber = ({ export const useGetPkiSubscriber = (
subscriberName, {
projectId subscriberName,
}: { projectId
subscriberName: string; }: {
projectId: string; subscriberName: string;
}) => { projectId: string;
},
options?: TReactQueryOptions["options"]
) => {
return useQuery({ return useQuery({
queryKey: pkiSubscriberKeys.getPkiSubscriber({ subscriberName, projectId }), queryKey: pkiSubscriberKeys.getPkiSubscriber({ subscriberName, projectId }),
queryFn: async () => { queryFn: async () => {
@@ -59,7 +62,8 @@ export const useGetPkiSubscriber = ({
); );
return pkiSubscriber; return pkiSubscriber;
}, },
enabled: Boolean(subscriberName) && Boolean(projectId) enabled: Boolean(subscriberName) && Boolean(projectId),
...options
}); });
}; };
@@ -5,6 +5,11 @@ export enum PkiSubscriberStatus {
DISABLED = "disabled" DISABLED = "disabled"
} }
export enum SubscriberOperationStatus {
SUCCESS = "success",
FAILED = "failed"
}
export type TPkiSubscriber = { export type TPkiSubscriber = {
id: string; id: string;
projectId: string; projectId: string;
@@ -17,6 +22,9 @@ export type TPkiSubscriber = {
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[];
supportsImmediateCertIssuance?: boolean; supportsImmediateCertIssuance?: boolean;
lastOperationStatus?: SubscriberOperationStatus;
lastOperationMessage?: string;
lastOperationAt?: string;
}; };
export type TCreatePkiSubscriberDTO = { export type TCreatePkiSubscriberDTO = {
@@ -56,7 +56,7 @@ export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen
limit: perPage limit: perPage
}, },
{ {
refetchInterval: 10 * 1000 // 10 seconds refetchInterval: 5000
} }
); );
@@ -5,7 +5,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, IconButton, Modal, ModalContent, Tooltip } from "@app/components/v2"; import {
Button,
GenericFieldLabel,
IconButton,
Modal,
ModalContent,
Tooltip
} from "@app/components/v2";
import { import {
ProjectPermissionPkiSubscriberActions, ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub, ProjectPermissionSub,
@@ -19,6 +26,7 @@ import {
useOrderPkiSubscriberCert useOrderPkiSubscriberCert
} from "@app/hooks/api"; } from "@app/hooks/api";
import { pkiSubscriberStatusToNameMap } from "@app/hooks/api/pkiSubscriber/constants"; import { pkiSubscriberStatusToNameMap } from "@app/hooks/api/pkiSubscriber/constants";
import { SubscriberOperationStatus } from "@app/hooks/api/pkiSubscriber/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "../../CertificatesPage/components/CertificateContent"; import { CertificateContent } from "../../CertificatesPage/components/CertificateContent";
@@ -45,10 +53,15 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
initialState: "Copy ID to clipboard" initialState: "Copy ID to clipboard"
}); });
const { data: pkiSubscriber } = useGetPkiSubscriber({ const { data: pkiSubscriber } = useGetPkiSubscriber(
subscriberName, {
projectId subscriberName,
}); projectId
},
{
refetchInterval: 5000
}
);
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } = const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
useIssuePkiSubscriberCert(); useIssuePkiSubscriberCert();
@@ -163,9 +176,26 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
<p className="text-sm font-semibold text-mineshaft-300">Common Name</p> <p className="text-sm font-semibold text-mineshaft-300">Common Name</p>
<p className="text-sm text-mineshaft-300">{pkiSubscriber.commonName}</p> <p className="text-sm text-mineshaft-300">{pkiSubscriber.commonName}</p>
</div> </div>
{pkiSubscriber.lastOperationAt && (
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Last Operation (Local Time)</p>
<p className="text-sm text-mineshaft-300">
{new Date(pkiSubscriber.lastOperationAt).toLocaleString()}
</p>
</div>
)}
{pkiSubscriber.lastOperationStatus === SubscriberOperationStatus.FAILED && (
<div className="mb-4">
<GenericFieldLabel labelClassName="text-red" label="Last Operation Status">
<p className="break-words rounded bg-mineshaft-600 p-2 text-xs">
{pkiSubscriber.lastOperationMessage}
</p>
</GenericFieldLabel>
</div>
)}
{canIssuePkiSubscriberCert && ( {canIssuePkiSubscriberCert && (
<Button <Button
className="mt-4 w-full" className="mt-2 w-full"
colorSchema="primary" colorSchema="primary"
type="button" type="button"
isLoading={isIssuingCert} isLoading={isIssuingCert}