mirror of
https://github.com/dyzulk/trustlab.git
synced 2026-01-26 05:25:36 +07:00
feat: implement Renew All (Bulk CA Chain Refresh) functionality
This commit is contained in:
@@ -14,38 +14,10 @@ import PageLoader from "@/components/ui/PageLoader";
|
||||
import { useTranslations } from "next-intl";
|
||||
import CdnManagementCard from "@/components/admin/CdnManagementCard";
|
||||
import ArchiveManagementTable from "@/components/admin/ArchiveManagementTable";
|
||||
import Button from "@/components/ui/button/Button";
|
||||
|
||||
const fetcher = (url: string) => axios.get(url).then((res) => res.data);
|
||||
|
||||
function SyncButton({ onClick, disabled, isLoading, label, variant = 'blue' }: { onClick: () => void, disabled: boolean, isLoading: boolean, label: string, variant?: 'blue' | 'gray' | 'purple' | 'indigo' }) {
|
||||
const variantClasses = {
|
||||
blue: 'bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400',
|
||||
gray: 'bg-gray-600 hover:bg-gray-700 disabled:bg-gray-400',
|
||||
purple: 'bg-purple-600 hover:bg-purple-700 disabled:bg-purple-400',
|
||||
indigo: 'bg-indigo-600 hover:bg-indigo-700 disabled:bg-indigo-400',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`flex items-center justify-center gap-2 px-3 py-1.5 ${variantClasses[variant]} text-white text-xs font-medium rounded-lg transition-all shadow-sm active:scale-95`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<svg className="animate-spin h-3.5 w-3.5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
||||
</svg>
|
||||
)}
|
||||
{isLoading ? "Syncing..." : label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootCaManagementClient() {
|
||||
const t = useTranslations("RootCA");
|
||||
const { user } = useAuth();
|
||||
@@ -53,9 +25,11 @@ export default function RootCaManagementClient() {
|
||||
const { addToast } = useToast();
|
||||
const { data, error, mutate, isLoading } = useSWR("/api/admin/ca-certificates", fetcher);
|
||||
const [isRenewing, setIsRenewing] = useState(false);
|
||||
const [isBulkRenewing, setIsBulkRenewing] = useState(false);
|
||||
const [activeSync, setActiveSync] = useState<string | null>(null);
|
||||
const [isPromoting, setIsPromoting] = useState(false);
|
||||
const [confirmRenewUuid, setConfirmRenewUuid] = useState<string | null>(null);
|
||||
const [showBulkRenewConfirm, setShowBulkRenewConfirm] = useState(false);
|
||||
|
||||
// Redirect if not admin or owner (double security, backend also checks)
|
||||
const { isAdminOrOwner } = useAuth();
|
||||
@@ -125,6 +99,23 @@ export default function RootCaManagementClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenewAll = async () => {
|
||||
setIsBulkRenewing(true);
|
||||
try {
|
||||
const response = await axios.post("/api/admin/ca-certificates/renew-all", { days: 3650 });
|
||||
if (response.data.status === "success") {
|
||||
addToast(t("toast_bulk_renew_success"), "success");
|
||||
mutate();
|
||||
setShowBulkRenewConfirm(false);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
addToast(err.response?.data?.message || t("toast_bulk_renew_failed"), "error");
|
||||
} finally {
|
||||
setIsBulkRenewing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (error) return <div className="p-10 text-center text-error-500">{t("load_failed")}</div>;
|
||||
|
||||
const certificates = data?.data || [];
|
||||
@@ -133,6 +124,15 @@ export default function RootCaManagementClient() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-2 gap-4">
|
||||
<PageBreadcrumb pageTitle={t("management_title")} />
|
||||
<Button
|
||||
variant="warning"
|
||||
size="sm"
|
||||
onClick={() => setShowBulkRenewConfirm(true)}
|
||||
loading={isBulkRenewing}
|
||||
disabled={isLoading || isRenewing || isPromoting}
|
||||
>
|
||||
{t("renew_all_button")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
@@ -196,6 +196,18 @@ export default function RootCaManagementClient() {
|
||||
variant="warning"
|
||||
requiredInput="RENEW"
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
isOpen={showBulkRenewConfirm}
|
||||
onClose={() => setShowBulkRenewConfirm(false)}
|
||||
onConfirm={handleRenewAll}
|
||||
title={t("bulk_renew_modal_title")}
|
||||
message={t("bulk_renew_modal_msg")}
|
||||
isLoading={isBulkRenewing}
|
||||
confirmLabel={t("bulk_renew_confirm_label")}
|
||||
variant="warning"
|
||||
requiredInput="RENEW-ALL"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { ReactNode } from "react";
|
||||
interface ButtonProps {
|
||||
children: ReactNode; // Button text or content
|
||||
size?: "sm" | "md"; // Button size
|
||||
variant?: "primary" | "outline" | "danger" | "success"; // Button variant
|
||||
variant?: "primary" | "outline" | "danger" | "success" | "warning"; // Button variant
|
||||
startIcon?: ReactNode; // Icon before the text
|
||||
endIcon?: ReactNode; // Icon after the text
|
||||
onClick?: () => void; // Click handler
|
||||
@@ -41,6 +41,8 @@ const Button: React.FC<ButtonProps> = ({
|
||||
"bg-red-600 text-white shadow-theme-xs hover:bg-red-700 disabled:bg-red-300 dark:bg-red-600 dark:hover:bg-red-700",
|
||||
success:
|
||||
"bg-green-600 text-white shadow-theme-xs hover:bg-green-700 disabled:bg-green-300 dark:bg-green-600 dark:hover:bg-green-700",
|
||||
warning:
|
||||
"bg-warning-500 text-white shadow-theme-xs hover:bg-warning-600 disabled:bg-warning-300 dark:bg-warning-600 dark:hover:bg-warning-700",
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -643,6 +643,12 @@
|
||||
"cdn_group_assets_desc": "Update specific file types to the CDN (Uses 'Both' mode by default).",
|
||||
"cdn_group_modes": "Dual CDN Strategy",
|
||||
"cdn_group_modes_desc": "Control asset distribution paths (Clean URLs vs Global Archives).",
|
||||
"renew_all_button": "Renew All (Chain-Refresh)",
|
||||
"bulk_renew_modal_title": "Renew Entire CA Chain?",
|
||||
"bulk_renew_modal_msg": "This will regenerate the Root CA and all Intermediate CAs sequentially. This is a significant action that will refresh the entire Chain of Trust. Type 'RENEW-ALL' to proceed.",
|
||||
"bulk_renew_confirm_label": "Renew Everything",
|
||||
"toast_bulk_renew_success": "Entire CA chain renewed successfully!",
|
||||
"toast_bulk_renew_failed": "Bulk renewal failed",
|
||||
"toast_renew_success": "CA Certificate renewed successfully.",
|
||||
"toast_renew_failed": "Failed to renew CA certificate",
|
||||
"load_failed": "Failed to load CA certificates. Admin access required.",
|
||||
|
||||
@@ -643,6 +643,12 @@
|
||||
"cdn_group_assets_desc": "Perbarui file spesifik ke CDN (Menggunakan mode 'Both' secara default).",
|
||||
"cdn_group_modes": "Strategi Dual CDN (Mode-specific)",
|
||||
"cdn_group_modes_desc": "Kontrol jalur distribusi aset (Clean URLs vs Global Archives).",
|
||||
"renew_all_button": "Pembaruan Massal (Renew All)",
|
||||
"bulk_renew_modal_title": "Perbarui Seluruh Rantai CA?",
|
||||
"bulk_renew_modal_msg": "Ini akan meregenerasi Root CA dan semua Intermediate CA secara berurutan. Ini adalah tindakan besar yang akan menyegarkan seluruh Rantai Kepercayaan (Chain of Trust). Ketik 'RENEW-ALL' untuk melanjutkan.",
|
||||
"bulk_renew_confirm_label": "Perbarui Semuanya",
|
||||
"toast_bulk_renew_success": "Seluruh rantai CA berhasil diperbarui!",
|
||||
"toast_bulk_renew_failed": "Pembaruan massal gagal",
|
||||
"toast_renew_success": "Sertifikat CA berhasil diperbarui.",
|
||||
"toast_renew_failed": "Gagal memperbarui sertifikat CA",
|
||||
"load_failed": "Gagal memuat sertifikat CA. Diperlukan akses admin.",
|
||||
|
||||
Reference in New Issue
Block a user