feat: add Purge CDN button and confirmation UI to CA management

This commit is contained in:
dyzulk
2026-01-07 11:07:04 +07:00
parent a1c38e4ab2
commit 2867288706
4 changed files with 69 additions and 4 deletions

View File

@@ -31,6 +31,8 @@ export default function RootCaManagementClient() {
const [isPromoting, setIsPromoting] = useState(false);
const [confirmRenewUuid, setConfirmRenewUuid] = useState<string | null>(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 */}
<CdnManagementCard
onSync={handleSyncCdn}
onPurge={() => 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"
/>
<ConfirmationModal
isOpen={showPurgeConfirm}
onClose={() => 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"
/>
</div>
);
}

View File

@@ -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
</div>
</ComponentCard>
</div>
{/* Danger Zone: Purge Control */}
<div className="mt-8 p-4 border border-error-100 dark:border-error-900/30 bg-error-50/30 dark:bg-error-900/10 rounded-xl">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex flex-col">
<span className="text-sm font-bold text-error-700 dark:text-error-400">{t("btn_purge_cdn")}</span>
<p className="text-xs text-error-600 dark:text-error-500/80 mt-1">{t("desc_purge_cdn")}</p>
</div>
<button
onClick={onPurge}
disabled={disabled || activeSync !== null}
className="flex items-center justify-center gap-2 px-6 py-2.5 bg-error-600 hover:bg-error-700 disabled:bg-error-400 text-white text-xs font-bold rounded-lg transition-all shadow-sm active:scale-95 whitespace-nowrap"
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
{t("btn_purge_cdn")}
</button>
</div>
</div>
</div>
);
}

View File

@@ -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",

View File

@@ -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",