From 28672887065d4adc8d93b03ee9667b143c40dc10 Mon Sep 17 00:00:00 2001 From: dyzulk <66510723+dyzulk@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:07:04 +0700 Subject: [PATCH] feat: add Purge CDN button and confirmation UI to CA management --- .../admin/root-ca/RootCaManagementClient.tsx | 32 ++++++++++++++++++- src/components/admin/CdnManagementCard.tsx | 23 ++++++++++++- src/messages/en.json | 9 +++++- src/messages/id.json | 9 +++++- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/app/dashboard/admin/root-ca/RootCaManagementClient.tsx b/src/app/dashboard/admin/root-ca/RootCaManagementClient.tsx index 3049c92..20c60f9 100644 --- a/src/app/dashboard/admin/root-ca/RootCaManagementClient.tsx +++ b/src/app/dashboard/admin/root-ca/RootCaManagementClient.tsx @@ -31,6 +31,8 @@ export default function RootCaManagementClient() { const [isPromoting, setIsPromoting] = useState(false); const [confirmRenewUuid, setConfirmRenewUuid] = useState(null); const [showBulkRenewConfirm, setShowBulkRenewConfirm] = useState(false); + const [showPurgeConfirm, setShowPurgeConfirm] = useState(false); + const [isPurging, setIsPurging] = useState(false); // Redirect if not admin or owner (double security, backend also checks) const { isAdminOrOwner } = useAuth(); @@ -82,6 +84,21 @@ export default function RootCaManagementClient() { } }; + const handlePurgeCdn = async () => { + setIsPurging(true); + try { + const response = await axios.post("/api/admin/ca-certificates/purge-cdn"); + addToast(response.data.message || t("toast_purge_success"), "success"); + setShowPurgeConfirm(false); + mutate(); // Refresh local list to see 'Local Only' status + } catch (err: any) { + console.error(err); + addToast(err.response?.data?.message || t("toast_purge_failed"), "error"); + } finally { + setIsPurging(false); + } + }; + const handlePromote = async (uuid: string) => { setIsPromoting(true); try { @@ -182,8 +199,9 @@ export default function RootCaManagementClient() { {/* CDN Synchronization Controls */} setShowPurgeConfirm(true)} activeSync={activeSync} - disabled={isLoading || isPromoting} + disabled={isLoading || isPromoting || isPurging} /> {/* Full-width Archive History */} @@ -232,6 +250,18 @@ export default function RootCaManagementClient() { variant="warning" requiredInput="RENEW-ALL" /> + + setShowPurgeConfirm(false)} + onConfirm={handlePurgeCdn} + title={t("purge_modal_title")} + message={t("purge_modal_msg")} + isLoading={isPurging} + confirmLabel={t("purge_confirm_label")} + variant="error" + requiredInput="PURGE" + /> ); } diff --git a/src/components/admin/CdnManagementCard.tsx b/src/components/admin/CdnManagementCard.tsx index 2a2fcb2..2dcbb30 100644 --- a/src/components/admin/CdnManagementCard.tsx +++ b/src/components/admin/CdnManagementCard.tsx @@ -6,11 +6,12 @@ import { useTranslations } from "next-intl"; interface CdnManagementCardProps { onSync: (type: "all" | "crt" | "installers" | "bundles", mode: "latest" | "archive" | "both") => void; + onPurge: () => void; activeSync: string | null; disabled: boolean; } -export default function CdnManagementCard({ onSync, activeSync, disabled }: CdnManagementCardProps) { +export default function CdnManagementCard({ onSync, onPurge, activeSync, disabled }: CdnManagementCardProps) { const t = useTranslations("RootCA"); const SyncButton = ({ @@ -130,6 +131,26 @@ export default function CdnManagementCard({ onSync, activeSync, disabled }: CdnM + + {/* Danger Zone: Purge Control */} +
+
+
+ {t("btn_purge_cdn")} +

{t("desc_purge_cdn")}

+
+ +
+
); } diff --git a/src/messages/en.json b/src/messages/en.json index 6f73f60..3e1da53 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -706,7 +706,14 @@ "label_latest_sync": "Latest Sync (Clean URLs)", "desc_latest_sync": "Only update the primary public links for installers.", "label_archive_sync": "Archive Sync (Versioned)", - "desc_archive_sync": "Only save permanent archives without changing public links." + "desc_archive_sync": "Only save permanent archives without changing public links.", + "btn_purge_cdn": "Purge CDN Assets", + "desc_purge_cdn": "PERMANENTLY delete all CA files from Cloudflare R2 and reset local sync status. Highly destructive.", + "purge_modal_title": "Purge All CDN Assets?", + "purge_modal_msg": "This action cannot be undone. All public certificates, installers, and bundles will be deleted from the CDN. Clients will not be able to download CA files until you re-sync.", + "purge_confirm_label": "Purge Everything", + "toast_purge_success": "CDN assets purged and local status reset successfully", + "toast_purge_failed": "Failed to purge CDN assets" }, "SmtpTester": { "page_title": "SMTP Tester", diff --git a/src/messages/id.json b/src/messages/id.json index 229396f..267da0a 100644 --- a/src/messages/id.json +++ b/src/messages/id.json @@ -706,7 +706,14 @@ "label_latest_sync": "Sinkronisasi Terbaru (URL Bersih)", "desc_latest_sync": "Hanya perbarui link publik utama pendukung installer.", "label_archive_sync": "Sinkronisasi Arsip (Versi)", - "desc_archive_sync": "Hanya simpan arsip permanen tanpa mengubah link publik." + "desc_archive_sync": "Hanya simpan arsip permanen tanpa mengubah link publik.", + "btn_purge_cdn": "Kosongkan Aset CDN", + "desc_purge_cdn": "Hapus secara PERMANEN semua file CA dari Cloudflare R2 dan reset status sinkronisasi lokal. Sangat destruktif.", + "purge_modal_title": "Kosongkan Semua Aset CDN?", + "purge_modal_msg": "Tindakan ini tidak dapat dibatalkan. Semua sertifikat publik, installer, dan bundle akan dihapus dari CDN. Client tidak akan bisa mengunduh file CA sampai Anda melakukan sinkronisasi ulang.", + "purge_confirm_label": "Hapus Semuanya", + "toast_purge_success": "Aset CDN dikosongkan dan status lokal berhasil direset", + "toast_purge_failed": "Gagal mengosongkan aset CDN" }, "SmtpTester": { "page_title": "Pengetes SMTP",