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

View File

@@ -88,6 +88,9 @@ export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.PkiSubscriber)) {
await knex.schema.alterTable(TableName.PkiSubscriber, (t) => {
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) {
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");
});
}
}

View File

@@ -19,7 +19,10 @@ export const PkiSubscribersSchema = z.object({
ttl: z.string().nullable().optional(),
keyUsages: 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>;

View File

@@ -180,7 +180,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
ttl: z
.string()
.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()
.describe(PKI_SUBSCRIBERS.UPDATE.ttl),
keyUsages: z

View File

@@ -86,8 +86,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
serialNumber: result.internalSerialNumber,
maxPathLength: result.internalMaxPathLength,
keyAlgorithm: result.internalKeyAlgorithm,
notBefore: result.internalNotBefore,
notAfter: result.internalNotAfter,
notBefore: result.internalNotBefore?.toISOString(),
notAfter: result.internalNotAfter?.toISOString(),
activeCaCertId: result.internalActiveCaCertId,
certificateAuthorityId: result.internalCertificateAuthorityId
}
@@ -232,8 +232,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
serialNumber: ca.internalSerialNumber,
maxPathLength: ca.internalMaxPathLength,
keyAlgorithm: ca.internalKeyAlgorithm,
notBefore: ca.internalNotBefore,
notAfter: ca.internalNotAfter,
notBefore: ca.internalNotBefore?.toISOString(),
notAfter: ca.internalNotAfter?.toISOString(),
activeCaCertId: ca.internalActiveCaCertId,
certificateAuthorityId: ca.internalCertificateAuthorityId
}

View File

@@ -19,6 +19,7 @@ import { TAppConnectionServiceFactory } from "../app-connection/app-connection-s
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-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 { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { CaType } from "./certificate-authority-enums";
@@ -47,7 +48,7 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
queueService: TQueueServiceFactory;
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById">;
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById" | "updateById">;
};
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
@@ -152,8 +153,20 @@ export const certificateAuthorityQueueFactory = ({
try {
if (caType === CaType.ACME) {
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}]`);
} finally {
await lock.release();

View File

@@ -18,7 +18,7 @@ const InternalCertificateAuthorityConfigurationSchema = z
organization: z.string().trim(),
ou: z.string().trim(),
dn: z.string().trim(),
parentCaId: z.string().uuid().optional(),
parentCaId: z.string().uuid().nullish(),
serialNumber: z.string().trim().optional(),
activeCaCertId: z.string().uuid().optional(),
country: z.string().trim(),

View File

@@ -12,7 +12,10 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
subjectAlternativeNames: true,
ttl: true,
keyUsages: true,
extendedKeyUsages: true
extendedKeyUsages: true,
lastOperationStatus: true,
lastOperationMessage: true,
lastOperationAt: true
}).extend({
supportsImmediateCertIssuance: z.boolean().optional()
});

View File

@@ -56,3 +56,8 @@ export type TListPkiSubscriberCertsDTO = {
offset: number;
limit: number;
} & TProjectPermission;
export enum SubscriberOperationStatus {
SUCCESS = "success",
FAILED = "failed"
}

View File

@@ -39,13 +39,16 @@ export const pkiSubscriberKeys = {
] as const
};
export const useGetPkiSubscriber = ({
subscriberName,
projectId
}: {
subscriberName: string;
projectId: string;
}) => {
export const useGetPkiSubscriber = (
{
subscriberName,
projectId
}: {
subscriberName: string;
projectId: string;
},
options?: TReactQueryOptions["options"]
) => {
return useQuery({
queryKey: pkiSubscriberKeys.getPkiSubscriber({ subscriberName, projectId }),
queryFn: async () => {
@@ -59,7 +62,8 @@ export const useGetPkiSubscriber = ({
);
return pkiSubscriber;
},
enabled: Boolean(subscriberName) && Boolean(projectId)
enabled: Boolean(subscriberName) && Boolean(projectId),
...options
});
};

View File

@@ -5,6 +5,11 @@ export enum PkiSubscriberStatus {
DISABLED = "disabled"
}
export enum SubscriberOperationStatus {
SUCCESS = "success",
FAILED = "failed"
}
export type TPkiSubscriber = {
id: string;
projectId: string;
@@ -17,6 +22,9 @@ export type TPkiSubscriber = {
keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
supportsImmediateCertIssuance?: boolean;
lastOperationStatus?: SubscriberOperationStatus;
lastOperationMessage?: string;
lastOperationAt?: string;
};
export type TCreatePkiSubscriberDTO = {

View File

@@ -56,7 +56,7 @@ export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen
limit: perPage
},
{
refetchInterval: 10 * 1000 // 10 seconds
refetchInterval: 5000
}
);

View File

@@ -5,7 +5,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
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 {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
@@ -19,6 +26,7 @@ import {
useOrderPkiSubscriberCert
} from "@app/hooks/api";
import { pkiSubscriberStatusToNameMap } from "@app/hooks/api/pkiSubscriber/constants";
import { SubscriberOperationStatus } from "@app/hooks/api/pkiSubscriber/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "../../CertificatesPage/components/CertificateContent";
@@ -45,10 +53,15 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
initialState: "Copy ID to clipboard"
});
const { data: pkiSubscriber } = useGetPkiSubscriber({
subscriberName,
projectId
});
const { data: pkiSubscriber } = useGetPkiSubscriber(
{
subscriberName,
projectId
},
{
refetchInterval: 5000
}
);
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
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 text-mineshaft-300">{pkiSubscriber.commonName}</p>
</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 && (
<Button
className="mt-4 w-full"
className="mt-2 w-full"
colorSchema="primary"
type="button"
isLoading={isIssuingCert}