Files
caddy-proxy-manager/app/(dashboard)/certificates/components/CaCertDrawer.tsx
akanealw 99819b70ff
Some checks failed
Build and Push Docker Images (Trusted) / build-and-push (., docker/caddy/Dockerfile, caddy) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/l4-port-manager/Dockerfile, l4-port-manager) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/web/Dockerfile, web) (push) Has been cancelled
Tests / test (push) Has been cancelled
added caddy-proxy-manager for testing
2026-04-21 22:49:08 +00:00

212 lines
7.7 KiB
TypeScript
Executable File

"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { useRef, useState, useTransition } from "react";
import {
createCaCertificateAction,
generateCaCertificateAction,
updateCaCertificateAction,
} from "../ca-actions";
import type { CaCertificateView } from "../page";
type Props = {
open: boolean;
cert: CaCertificateView | null;
onClose: () => void;
};
export function CaCertDrawer({ open, cert, onClose }: Props) {
const isEdit = cert !== null;
const [tab, setTab] = useState<"generate" | "import">("generate");
const [isPending, startTransition] = useTransition();
const generateRef = useRef<HTMLFormElement>(null);
const importRef = useRef<HTMLFormElement>(null);
const editRef = useRef<HTMLFormElement>(null);
function handleClose() {
setTab("generate");
onClose();
}
function handleGenerate(e: React.FormEvent) {
e.preventDefault();
const formData = new FormData(generateRef.current!);
startTransition(async () => {
await generateCaCertificateAction(formData);
handleClose();
});
}
function handleImport(e: React.FormEvent) {
e.preventDefault();
const formData = new FormData(importRef.current!);
startTransition(async () => {
await createCaCertificateAction(formData);
handleClose();
});
}
function handleEdit(e: React.FormEvent) {
e.preventDefault();
const formData = new FormData(editRef.current!);
startTransition(async () => {
await updateCaCertificateAction(cert!.id, formData);
handleClose();
});
}
return (
<Sheet open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>
<SheetContent side="right" className="w-full sm:w-[480px] sm:max-w-[480px] flex flex-col gap-6 overflow-y-auto">
<SheetHeader>
<SheetTitle>{isEdit ? "Edit CA Certificate" : "Add CA Certificate"}</SheetTitle>
</SheetHeader>
{isEdit ? (
/* Edit form */
<form
ref={editRef}
onSubmit={handleEdit}
className="flex flex-col gap-4 flex-1"
>
<div className="flex flex-col gap-1.5">
<Label htmlFor="edit-name">Name</Label>
<Input
id="edit-name"
name="name"
required
defaultValue={cert.name}
autoFocus
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="edit-cert-pem">Certificate PEM</Label>
<Textarea
id="edit-cert-pem"
name="certificate_pem"
required
defaultValue={cert.certificatePem}
rows={8}
className="font-mono text-xs"
/>
<p className="text-xs text-muted-foreground">PEM-encoded X.509 CA certificate</p>
</div>
<div className="flex gap-2 justify-end mt-auto pt-2">
<Button type="button" variant="outline" onClick={handleClose} disabled={isPending}>
Cancel
</Button>
<Button type="submit" disabled={isPending}>
{isPending ? "Saving..." : "Save"}
</Button>
</div>
</form>
) : (
/* Add: Generate / Import tabs */
<Tabs value={tab} onValueChange={(v) => setTab(v as "generate" | "import")} className="flex flex-col gap-4 flex-1">
<TabsList className="w-full">
<TabsTrigger value="generate" className="flex-1">Generate</TabsTrigger>
<TabsTrigger value="import" className="flex-1">Import PEM</TabsTrigger>
</TabsList>
<TabsContent value="generate">
<form
ref={generateRef}
onSubmit={handleGenerate}
className="flex flex-col gap-4"
>
<div className="flex flex-col gap-1.5">
<Label htmlFor="gen-name">Name</Label>
<Input
id="gen-name"
name="name"
required
autoFocus
placeholder="My Client CA"
/>
<p className="text-xs text-muted-foreground">Display name in this UI</p>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="gen-cn">Common Name (CN)</Label>
<Input
id="gen-cn"
name="common_name"
placeholder="My Client CA"
/>
<p className="text-xs text-muted-foreground">CN field in the certificate. Defaults to the name above if left blank.</p>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="gen-validity">Validity</Label>
<div className="flex items-center gap-2">
<Input
id="gen-validity"
name="validity_days"
type="number"
defaultValue={3650}
min={1}
max={3650}
className="flex-1"
/>
<span className="text-sm text-muted-foreground">days</span>
</div>
</div>
<div className="flex gap-2 justify-end mt-auto pt-2">
<Button type="button" variant="outline" onClick={handleClose} disabled={isPending}>
Cancel
</Button>
<Button type="submit" disabled={isPending}>
{isPending ? "Generating..." : "Generate CA Certificate"}
</Button>
</div>
</form>
</TabsContent>
<TabsContent value="import">
<form
ref={importRef}
onSubmit={handleImport}
className="flex flex-col gap-4"
>
<div className="flex flex-col gap-1.5">
<Label htmlFor="imp-name">Name</Label>
<Input
id="imp-name"
name="name"
required
autoFocus
placeholder="My Client CA"
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="imp-cert-pem">Certificate PEM</Label>
<Textarea
id="imp-cert-pem"
name="certificate_pem"
required
rows={8}
placeholder={"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"}
className="font-mono text-xs"
/>
<p className="text-xs text-muted-foreground">PEM-encoded X.509 CA certificate (no private key needed)</p>
</div>
<div className="flex gap-2 justify-end mt-auto pt-2">
<Button type="button" variant="outline" onClick={handleClose} disabled={isPending}>
Cancel
</Button>
<Button type="submit" disabled={isPending}>
{isPending ? "Adding..." : "Add CA Certificate"}
</Button>
</div>
</form>
</TabsContent>
</Tabs>
)}
</SheetContent>
</Sheet>
);
}