mirror of
https://github.com/dyzulk/trustlab.git
synced 2026-01-26 05:25:36 +07:00
214 lines
7.7 KiB
TypeScript
214 lines
7.7 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import useSWR from "swr";
|
|
import axios from "@/lib/axios";
|
|
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
|
|
import ComponentCard from "@/components/common/ComponentCard";
|
|
import RootCaTable from "@/components/admin/RootCaTable";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import { useRouter } from "next/navigation";
|
|
import { useToast } from "@/context/ToastContext";
|
|
import ConfirmationModal from "@/components/common/ConfirmationModal";
|
|
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);
|
|
|
|
export default function RootCaManagementClient() {
|
|
const t = useTranslations("RootCA");
|
|
const { user } = useAuth();
|
|
const router = useRouter();
|
|
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();
|
|
React.useEffect(() => {
|
|
if (user && !isAdminOrOwner) {
|
|
router.push("/dashboard");
|
|
}
|
|
}, [user, isAdminOrOwner, router]);
|
|
|
|
const handleRenew = async (uuid: string) => {
|
|
setIsRenewing(true);
|
|
try {
|
|
await axios.post(`/api/admin/ca-certificates/${uuid}/renew`, {
|
|
days: 3650
|
|
});
|
|
mutate();
|
|
addToast(t("toast_renew_success"), "success");
|
|
setConfirmRenewUuid(null);
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
addToast(err.response?.data?.message || t("toast_renew_failed"), "error");
|
|
} finally {
|
|
setIsRenewing(false);
|
|
}
|
|
};
|
|
|
|
const handleSyncCdn = async (type: 'all' | 'crt' | 'installers' | 'bundles', mode: 'latest' | 'archive' | 'both' = 'both') => {
|
|
const syncKey = `${type}-${mode}`;
|
|
setActiveSync(syncKey);
|
|
try {
|
|
let endpoint = "/api/admin/ca-certificates/sync-cdn";
|
|
let msg = `Sync successful (Mode: ${mode})`;
|
|
|
|
if (type === 'crt') {
|
|
endpoint = "/api/admin/ca-certificates/sync-crt";
|
|
msg = `CRT Files Sync successful (${mode})`;
|
|
} else if (type === 'installers') {
|
|
endpoint = "/api/admin/ca-certificates/sync-installers";
|
|
msg = `Individual Installers Sync successful (${mode})`;
|
|
} else if (type === 'bundles') {
|
|
endpoint = "/api/admin/ca-certificates/sync-bundles";
|
|
msg = "Global Bundles Sync successful";
|
|
}
|
|
|
|
const response = await axios.post(endpoint, { mode });
|
|
addToast(response.data.message || msg, "success");
|
|
mutate();
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
addToast(err.response?.data?.message || "Sync failed", "error");
|
|
} finally {
|
|
setActiveSync(null);
|
|
}
|
|
};
|
|
|
|
const handlePromote = async (uuid: string) => {
|
|
setIsPromoting(true);
|
|
try {
|
|
const response = await axios.post(`/api/admin/ca-certificates/${uuid}/promote`);
|
|
addToast(response.data.message || "Promoted successfully", "success");
|
|
mutate();
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
addToast(err.response?.data?.message || "Promotion failed", "error");
|
|
} finally {
|
|
setIsPromoting(false);
|
|
}
|
|
};
|
|
|
|
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 || [];
|
|
|
|
return (
|
|
<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">
|
|
{/* Main CA Management Table */}
|
|
<ComponentCard
|
|
title={t("card_title")}
|
|
desc={t("card_desc")}
|
|
className="relative"
|
|
>
|
|
<RootCaTable
|
|
certificates={certificates.filter((c: any) => c.is_latest || certificates.length === 1)}
|
|
onRenew={setConfirmRenewUuid}
|
|
isRenewing={isRenewing}
|
|
/>
|
|
|
|
{(isLoading || isRenewing) && (
|
|
<div className="absolute inset-0 bg-white/50 dark:bg-gray-900/50 flex items-center justify-center z-10 backdrop-blur-sm rounded-2xl">
|
|
<PageLoader text={t("processing")} className="h-full" />
|
|
</div>
|
|
)}
|
|
</ComponentCard>
|
|
|
|
{/* CDN Synchronization Controls */}
|
|
<CdnManagementCard
|
|
onSync={handleSyncCdn}
|
|
activeSync={activeSync}
|
|
disabled={isLoading || isPromoting}
|
|
/>
|
|
|
|
{/* Full-width Archive History */}
|
|
<ComponentCard
|
|
title="Version Archives"
|
|
desc="Browse historical versions and promote them back to Latest if needed. This table provides full visibility into your CDN audit trail."
|
|
>
|
|
<ArchiveManagementTable
|
|
certificates={certificates}
|
|
onPromote={handlePromote}
|
|
isPromoting={isPromoting}
|
|
/>
|
|
</ComponentCard>
|
|
|
|
<div className="p-4 bg-blue-50 border-l-4 border-blue-400 dark:bg-blue-900/20 dark:border-blue-600 rounded-md">
|
|
<h4 className="text-sm font-bold text-blue-800 dark:text-blue-200">{t("info_title")}</h4>
|
|
<ul className="mt-2 text-xs text-blue-700 dark:text-blue-300 list-disc list-inside space-y-1">
|
|
<li>{t("info_point_1")}</li>
|
|
<li>{t("info_point_2")}</li>
|
|
<li>{t("info_point_3")}</li>
|
|
<li>{t("info_point_4")}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<ConfirmationModal
|
|
isOpen={confirmRenewUuid !== null}
|
|
onClose={() => setConfirmRenewUuid(null)}
|
|
onConfirm={() => confirmRenewUuid && handleRenew(confirmRenewUuid)}
|
|
title={t("renew_modal_title")}
|
|
message={t("renew_modal_msg")}
|
|
isLoading={isRenewing}
|
|
confirmLabel={t("renew_modal_confirm")}
|
|
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>
|
|
);
|
|
}
|