mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge pull request #3350 from Bima42/feat/3325-add-button-to-edit-certificates
feat: be able to edit certificate
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { HelpCircle, PlusIcon } from "lucide-react";
|
import { HelpCircle, PlusIcon, SquarePen } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -47,108 +47,157 @@ const certificateDataHolder =
|
|||||||
const privateKeyDataHolder =
|
const privateKeyDataHolder =
|
||||||
"-----BEGIN PRIVATE KEY-----\nMIIFRDCCAyygAwIBAgIUEPOR47ys6VDwMVB9tYoeEka83uQwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwObWktZG9taW5pby5jb20wHhcNMjQwMzExMDQyNzU3WhcN\n-----END PRIVATE KEY-----";
|
"-----BEGIN PRIVATE KEY-----\nMIIFRDCCAyygAwIBAgIUEPOR47ys6VDwMVB9tYoeEka83uQwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwObWktZG9taW5pby5jb20wHhcNMjQwMzExMDQyNzU3WhcN\n-----END PRIVATE KEY-----";
|
||||||
|
|
||||||
const addCertificate = z.object({
|
const handleCertificateSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
certificateData: z.string().min(1, "Certificate data is required"),
|
certificateData: z.string().min(1, "Certificate data is required"),
|
||||||
privateKey: z.string().min(1, "Private key is required"),
|
privateKey: z.string().min(1, "Private key is required"),
|
||||||
autoRenew: z.boolean().optional(),
|
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddCertificate = z.infer<typeof addCertificate>;
|
type HandleCertificateForm = z.infer<typeof handleCertificateSchema>;
|
||||||
|
|
||||||
export const AddCertificate = () => {
|
interface Props {
|
||||||
|
certificateId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HandleCertificate = ({ certificateId }: Props) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
const { mutateAsync, isError, error, isPending } =
|
|
||||||
api.certificates.create.useMutation();
|
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
// Show dropdown logic based on cloud environment
|
const shouldShowServerDropdown = hasServers && !certificateId; // Hide on edit
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
const form = useForm<AddCertificate>({
|
const { data: existingCert, refetch } = api.certificates.one.useQuery(
|
||||||
|
{ certificateId: certificateId || "" },
|
||||||
|
{ enabled: !!certificateId },
|
||||||
|
);
|
||||||
|
|
||||||
|
const createMutation = api.certificates.create.useMutation();
|
||||||
|
const updateMutation = api.certificates.update.useMutation();
|
||||||
|
const mutation = certificateId ? updateMutation : createMutation;
|
||||||
|
const { mutateAsync, isError, error, isPending } = mutation;
|
||||||
|
|
||||||
|
const form = useForm<HandleCertificateForm>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
certificateData: "",
|
certificateData: "",
|
||||||
privateKey: "",
|
privateKey: "",
|
||||||
autoRenew: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addCertificate),
|
resolver: zodResolver(handleCertificateSchema),
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
|
||||||
form.reset();
|
|
||||||
}, [form, form.formState.isSubmitSuccessful, form.reset]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: AddCertificate) => {
|
useEffect(() => {
|
||||||
await mutateAsync({
|
if (existingCert) {
|
||||||
|
form.reset({
|
||||||
|
name: existingCert.name,
|
||||||
|
certificateData: existingCert.certificateData,
|
||||||
|
privateKey: existingCert.privateKey,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
form.reset({
|
||||||
|
name: "",
|
||||||
|
certificateData: "",
|
||||||
|
privateKey: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [existingCert, form, open]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: HandleCertificateForm) => {
|
||||||
|
const basePayload = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
certificateData: data.certificateData,
|
certificateData: data.certificateData,
|
||||||
privateKey: data.privateKey,
|
privateKey: data.privateKey,
|
||||||
autoRenew: data.autoRenew,
|
};
|
||||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
|
||||||
organizationId: "",
|
const promise = certificateId
|
||||||
})
|
? updateMutation.mutateAsync({
|
||||||
|
certificateId,
|
||||||
|
...basePayload,
|
||||||
|
})
|
||||||
|
: createMutation.mutateAsync({
|
||||||
|
...basePayload,
|
||||||
|
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||||
|
organizationId: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Certificate Created");
|
toast.success(
|
||||||
|
certificateId ? "Certificate Updated" : "Certificate Created",
|
||||||
|
);
|
||||||
await utils.certificates.all.invalidate();
|
await utils.certificates.all.invalidate();
|
||||||
|
if (certificateId) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error creating the Certificate");
|
toast.error(
|
||||||
|
certificateId
|
||||||
|
? "Error updating the Certificate"
|
||||||
|
: "Error creating the Certificate",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>
|
{certificateId ? (
|
||||||
{" "}
|
<Button
|
||||||
<PlusIcon className="h-4 w-4" />
|
variant="ghost"
|
||||||
Add Certificate
|
size="icon"
|
||||||
</Button>
|
className="group hover:bg-blue-500/10"
|
||||||
|
>
|
||||||
|
<SquarePen className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button>
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
Add Certificate
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add New Certificate</DialogTitle>
|
<DialogTitle>
|
||||||
|
{certificateId ? "Update" : "Add New"} Certificate
|
||||||
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Upload or generate a certificate to secure your application
|
{certificateId
|
||||||
|
? "Modify the certificate details"
|
||||||
|
: "Upload or generate a certificate to secure your application"}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
id="hook-form-add-certificate"
|
id="hook-form-handle-certificate"
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="grid w-full gap-4 "
|
className="grid w-full gap-4"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => {
|
render={({ field }) => (
|
||||||
return (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel>Certificate Name</FormLabel>
|
||||||
<FormLabel>Certificate Name</FormLabel>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder="My Certificate" {...field} />
|
||||||
<Input placeholder={"My Certificate"} {...field} />
|
</FormControl>
|
||||||
</FormControl>
|
<FormMessage />
|
||||||
<FormMessage />
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="certificateData"
|
name="certificateData"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div className="space-y-0.5">
|
<FormLabel>Certificate Data</FormLabel>
|
||||||
<FormLabel>Certificate Data</FormLabel>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="h-32"
|
className="h-32"
|
||||||
@@ -165,9 +214,7 @@ export const AddCertificate = () => {
|
|||||||
name="privateKey"
|
name="privateKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div className="space-y-0.5">
|
<FormLabel>Private Key</FormLabel>
|
||||||
<FormLabel>Private Key</FormLabel>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="h-32"
|
className="h-32"
|
||||||
@@ -248,10 +295,10 @@ export const AddCertificate = () => {
|
|||||||
<DialogFooter className="flex w-full flex-row !justify-end">
|
<DialogFooter className="flex w-full flex-row !justify-end">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
form="hook-form-add-certificate"
|
form="hook-form-handle-certificate"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Create
|
{certificateId ? "Update" : "Create"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
Link,
|
Link,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Server,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AddCertificate } from "./add-certificate";
|
import { HandleCertificate } from "./handle-certificate";
|
||||||
import {
|
import {
|
||||||
extractLeafCommonName,
|
extractLeafCommonName,
|
||||||
getCertificateChainExpirationDetails,
|
getCertificateChainExpirationDetails,
|
||||||
@@ -69,7 +70,7 @@ export const ShowCertificates = () => {
|
|||||||
<span className="text-base text-muted-foreground text-center">
|
<span className="text-base text-muted-foreground text-center">
|
||||||
You don't have any certificates created
|
You don't have any certificates created
|
||||||
</span>
|
</span>
|
||||||
{permissions?.certificate.create && <AddCertificate />}
|
{permissions?.certificate.create && <HandleCertificate />}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-4 min-h-[25vh]">
|
<div className="flex flex-col gap-4 min-h-[25vh]">
|
||||||
@@ -121,6 +122,12 @@ export const ShowCertificates = () => {
|
|||||||
CN: {commonName}
|
CN: {commonName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
||||||
|
<Server className="size-3" />
|
||||||
|
{certificate.server
|
||||||
|
? `${certificate.server.name} (${certificate.server.ipAddress})`
|
||||||
|
: "Dokploy (Local)"}
|
||||||
|
</span>
|
||||||
{chainInfo.isChain && (
|
{chainInfo.isChain && (
|
||||||
<div className="flex flex-col gap-1.5 mt-1">
|
<div className="flex flex-col gap-1.5 mt-1">
|
||||||
<button
|
<button
|
||||||
@@ -181,8 +188,14 @@ export const ShowCertificates = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{permissions?.certificate.delete && (
|
<div className="flex flex-row gap-1">
|
||||||
<div className="flex flex-row gap-1">
|
{permissions?.certificate.update && (
|
||||||
|
<HandleCertificate
|
||||||
|
certificateId={certificate.certificateId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions?.certificate.delete && (
|
||||||
<DialogAction
|
<DialogAction
|
||||||
title="Delete Certificate"
|
title="Delete Certificate"
|
||||||
description="Are you sure you want to delete this certificate?"
|
description="Are you sure you want to delete this certificate?"
|
||||||
@@ -208,14 +221,14 @@ export const ShowCertificates = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="group hover:bg-red-500/10 "
|
className="group hover:bg-red-500/10"
|
||||||
isLoading={isRemoving}
|
isLoading={isRemoving}
|
||||||
>
|
>
|
||||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogAction>
|
</DialogAction>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -224,7 +237,7 @@ export const ShowCertificates = () => {
|
|||||||
|
|
||||||
{permissions?.certificate.create && (
|
{permissions?.certificate.create && (
|
||||||
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4">
|
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4">
|
||||||
<AddCertificate />
|
<HandleCertificate />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ export const extractExpirationDate = (certData: string): Date | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip the outer certificate sequence
|
// Skip the outer certificate sequence
|
||||||
if (der[offset++] !== 0x30) throw new Error("Expected sequence");
|
if (der[offset++] !== 0x30) return null;
|
||||||
({ offset } = readLength(offset));
|
({ offset } = readLength(offset));
|
||||||
|
|
||||||
// Skip tbsCertificate sequence
|
// Skip tbsCertificate sequence
|
||||||
if (der[offset++] !== 0x30) throw new Error("Expected tbsCertificate");
|
if (der[offset++] !== 0x30) return null;
|
||||||
({ offset } = readLength(offset));
|
({ offset } = readLength(offset));
|
||||||
|
|
||||||
// Check for optional version field (context-specific tag [0])
|
// Check for optional version field (context-specific tag [0])
|
||||||
@@ -52,15 +52,14 @@ export const extractExpirationDate = (certData: string): Date | null => {
|
|||||||
|
|
||||||
// Skip serialNumber, signature, issuer
|
// Skip serialNumber, signature, issuer
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
if (der[offset] !== 0x30 && der[offset] !== 0x02)
|
if (der[offset] !== 0x30 && der[offset] !== 0x02) return null;
|
||||||
throw new Error("Unexpected structure");
|
|
||||||
offset++;
|
offset++;
|
||||||
const fieldLen = readLength(offset);
|
const fieldLen = readLength(offset);
|
||||||
offset = fieldLen.offset + fieldLen.length;
|
offset = fieldLen.offset + fieldLen.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validity sequence (notBefore and notAfter)
|
// Validity sequence (notBefore and notAfter)
|
||||||
if (der[offset++] !== 0x30) throw new Error("Expected validity sequence");
|
if (der[offset++] !== 0x30) return null;
|
||||||
const validityLen = readLength(offset);
|
const validityLen = readLength(offset);
|
||||||
offset = validityLen.offset;
|
offset = validityLen.offset;
|
||||||
|
|
||||||
@@ -138,11 +137,11 @@ export const extractCommonName = (certData: string): string | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip the outer certificate sequence
|
// Skip the outer certificate sequence
|
||||||
if (der[offset++] !== 0x30) throw new Error("Expected sequence");
|
if (der[offset++] !== 0x30) return null;
|
||||||
({ offset } = readLength(offset));
|
({ offset } = readLength(offset));
|
||||||
|
|
||||||
// Skip tbsCertificate sequence
|
// Skip tbsCertificate sequence
|
||||||
if (der[offset++] !== 0x30) throw new Error("Expected tbsCertificate");
|
if (der[offset++] !== 0x30) return null;
|
||||||
({ offset } = readLength(offset));
|
({ offset } = readLength(offset));
|
||||||
|
|
||||||
// Check for optional version field (context-specific tag [0])
|
// Check for optional version field (context-specific tag [0])
|
||||||
@@ -165,7 +164,7 @@ export const extractCommonName = (certData: string): string | null => {
|
|||||||
offset = skipField(offset);
|
offset = skipField(offset);
|
||||||
|
|
||||||
// Subject sequence - where we find the CN
|
// Subject sequence - where we find the CN
|
||||||
if (der[offset++] !== 0x30) throw new Error("Expected subject sequence");
|
if (der[offset++] !== 0x30) return null;
|
||||||
const subjectLen = readLength(offset);
|
const subjectLen = readLength(offset);
|
||||||
const subjectEnd = subjectLen.offset + subjectLen.length;
|
const subjectEnd = subjectLen.offset + subjectLen.length;
|
||||||
offset = subjectLen.offset;
|
offset = subjectLen.offset;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
findCertificateById,
|
findCertificateById,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
removeCertificateById,
|
removeCertificateById,
|
||||||
|
updateCertificate,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
@@ -12,6 +13,7 @@ import { audit } from "@/server/api/utils/audit";
|
|||||||
import {
|
import {
|
||||||
apiCreateCertificate,
|
apiCreateCertificate,
|
||||||
apiFindCertificate,
|
apiFindCertificate,
|
||||||
|
apiUpdateCertificate,
|
||||||
certificates,
|
certificates,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
|
||||||
@@ -72,6 +74,25 @@ export const certificateRouter = createTRPCRouter({
|
|||||||
all: withPermission("certificate", "read").query(async ({ ctx }) => {
|
all: withPermission("certificate", "read").query(async ({ ctx }) => {
|
||||||
return await db.query.certificates.findMany({
|
return await db.query.certificates.findMany({
|
||||||
where: eq(certificates.organizationId, ctx.session.activeOrganizationId),
|
where: eq(certificates.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
with: {
|
||||||
|
server: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
update: withPermission("certificate", "update")
|
||||||
|
.input(apiUpdateCertificate)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const certificate = await findCertificateById(input.certificateId);
|
||||||
|
if (certificate.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not allowed to update this certificate",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await updateCertificate(input.certificateId, {
|
||||||
|
name: input.name,
|
||||||
|
certificateData: input.certificateData,
|
||||||
|
privateKey: input.privateKey,
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export const apiUpdateCertificate = z.object({
|
|||||||
name: z.string().min(1).optional(),
|
name: z.string().min(1).optional(),
|
||||||
certificateData: z.string().min(1).optional(),
|
certificateData: z.string().min(1).optional(),
|
||||||
privateKey: z.string().min(1).optional(),
|
privateKey: z.string().min(1).optional(),
|
||||||
autoRenew: z.boolean().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiDeleteCertificate = z.object({
|
export const apiDeleteCertificate = z.object({
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const statements = {
|
|||||||
environmentEnvVars: ["read", "write"],
|
environmentEnvVars: ["read", "write"],
|
||||||
server: ["read", "create", "delete"],
|
server: ["read", "create", "delete"],
|
||||||
registry: ["read", "create", "delete"],
|
registry: ["read", "create", "delete"],
|
||||||
certificate: ["read", "create", "delete"],
|
certificate: ["read", "create", "update", "delete"],
|
||||||
backup: ["read", "create", "update", "delete", "restore"],
|
backup: ["read", "create", "update", "delete", "restore"],
|
||||||
volumeBackup: ["read", "create", "update", "delete", "restore"],
|
volumeBackup: ["read", "create", "update", "delete", "restore"],
|
||||||
schedule: ["read", "create", "update", "delete"],
|
schedule: ["read", "create", "update", "delete"],
|
||||||
@@ -102,7 +102,7 @@ export const ownerRole = ac.newRole({
|
|||||||
environmentEnvVars: ["read", "write"],
|
environmentEnvVars: ["read", "write"],
|
||||||
server: ["read", "create", "delete"],
|
server: ["read", "create", "delete"],
|
||||||
registry: ["read", "create", "delete"],
|
registry: ["read", "create", "delete"],
|
||||||
certificate: ["read", "create", "delete"],
|
certificate: ["read", "create", "update", "delete"],
|
||||||
backup: ["read", "create", "update", "delete", "restore"],
|
backup: ["read", "create", "update", "delete", "restore"],
|
||||||
volumeBackup: ["read", "create", "update", "delete", "restore"],
|
volumeBackup: ["read", "create", "update", "delete", "restore"],
|
||||||
schedule: ["read", "create", "update", "delete"],
|
schedule: ["read", "create", "update", "delete"],
|
||||||
@@ -139,7 +139,7 @@ export const adminRole = ac.newRole({
|
|||||||
environmentEnvVars: ["read", "write"],
|
environmentEnvVars: ["read", "write"],
|
||||||
server: ["read", "create", "delete"],
|
server: ["read", "create", "delete"],
|
||||||
registry: ["read", "create", "delete"],
|
registry: ["read", "create", "delete"],
|
||||||
certificate: ["read", "create", "delete"],
|
certificate: ["read", "create", "update", "delete"],
|
||||||
backup: ["read", "create", "update", "delete", "restore"],
|
backup: ["read", "create", "update", "delete", "restore"],
|
||||||
volumeBackup: ["read", "create", "update", "delete", "restore"],
|
volumeBackup: ["read", "create", "update", "delete", "restore"],
|
||||||
schedule: ["read", "create", "update", "delete"],
|
schedule: ["read", "create", "update", "delete"],
|
||||||
|
|||||||
@@ -126,3 +126,36 @@ const createCertificateFiles = async (certificate: Certificate) => {
|
|||||||
fs.writeFileSync(configFile, yamlConfig);
|
fs.writeFileSync(configFile, yamlConfig);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateCertificate = async (
|
||||||
|
certificateId: string,
|
||||||
|
updates: {
|
||||||
|
name?: string;
|
||||||
|
certificateData?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const updated = await db
|
||||||
|
.update(certificates)
|
||||||
|
.set({
|
||||||
|
...updates,
|
||||||
|
})
|
||||||
|
.where(eq(certificates.certificateId, certificateId))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!updated || updated[0] === undefined) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Failed to update the certificate",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cert = updated[0];
|
||||||
|
|
||||||
|
// If cert data or private key changed, rewrite files
|
||||||
|
if (updates.certificateData || updates.privateKey) {
|
||||||
|
await createCertificateFiles(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user