mirror of
https://github.com/dyzulk/trustlab.git
synced 2026-01-26 13:32:06 +07:00
feat: implement dual CDN strategy and archive management UI
This commit is contained in:
@@ -12,6 +12,8 @@ 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";
|
||||
|
||||
const fetcher = (url: string) => axios.get(url).then((res) => res.data);
|
||||
|
||||
@@ -52,6 +54,7 @@ export default function RootCaManagementClient() {
|
||||
const { data, error, mutate, isLoading } = useSWR("/api/admin/ca-certificates", fetcher);
|
||||
const [isRenewing, setIsRenewing] = useState(false);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [isPromoting, setIsPromoting] = useState(false);
|
||||
const [confirmRenewUuid, setConfirmRenewUuid] = useState<string | null>(null);
|
||||
|
||||
// Redirect if not admin or owner (double security, backend also checks)
|
||||
@@ -79,24 +82,24 @@ export default function RootCaManagementClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSyncCdn = async (type: 'all' | 'crt' | 'installers' | 'bundles') => {
|
||||
const handleSyncCdn = async (type: 'all' | 'crt' | 'installers' | 'bundles', mode: 'latest' | 'archive' | 'both' = 'both') => {
|
||||
setIsSyncing(true);
|
||||
try {
|
||||
let endpoint = "/api/admin/ca-certificates/sync-cdn";
|
||||
let msg = "Full Sync successful";
|
||||
let msg = `Sync successful (Mode: ${mode})`;
|
||||
|
||||
if (type === 'crt') {
|
||||
endpoint = "/api/admin/ca-certificates/sync-crt";
|
||||
msg = "CRT Files Sync successful";
|
||||
msg = `CRT Files Sync successful (${mode})`;
|
||||
} else if (type === 'installers') {
|
||||
endpoint = "/api/admin/ca-certificates/sync-installers";
|
||||
msg = "Individual Installers Sync successful";
|
||||
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);
|
||||
const response = await axios.post(endpoint, { mode });
|
||||
addToast(response.data.message || msg, "success");
|
||||
mutate();
|
||||
} catch (err: any) {
|
||||
@@ -107,6 +110,20 @@ export default function RootCaManagementClient() {
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
if (error) return <div className="p-10 text-center text-error-500">{t("load_failed")}</div>;
|
||||
|
||||
const certificates = data?.data || [];
|
||||
@@ -115,66 +132,58 @@ export default function RootCaManagementClient() {
|
||||
<div>
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-4 gap-4">
|
||||
<PageBreadcrumb pageTitle={t("management_title")} />
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<SyncButton
|
||||
onClick={() => handleSyncCdn('crt')}
|
||||
disabled={isSyncing || isLoading}
|
||||
isLoading={isSyncing}
|
||||
label="Sync CRT Only"
|
||||
variant="gray"
|
||||
/>
|
||||
<SyncButton
|
||||
onClick={() => handleSyncCdn('installers')}
|
||||
disabled={isSyncing || isLoading}
|
||||
isLoading={isSyncing}
|
||||
label="Sync Installers Only"
|
||||
variant="blue"
|
||||
/>
|
||||
<SyncButton
|
||||
onClick={() => handleSyncCdn('bundles')}
|
||||
disabled={isSyncing || isLoading}
|
||||
isLoading={isSyncing}
|
||||
label="Sync Full Bundles"
|
||||
variant="purple"
|
||||
/>
|
||||
<SyncButton
|
||||
onClick={() => handleSyncCdn('all')}
|
||||
disabled={isSyncing || isLoading}
|
||||
isLoading={isSyncing}
|
||||
label="Sync All to CDN"
|
||||
variant="indigo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<ComponentCard
|
||||
title={t("card_title")}
|
||||
desc={t("card_desc")}
|
||||
className="relative"
|
||||
>
|
||||
<RootCaTable
|
||||
certificates={certificates}
|
||||
onRenew={setConfirmRenewUuid}
|
||||
isRenewing={isRenewing}
|
||||
/>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Left Column: Management & Renew Table */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<CdnManagementCard
|
||||
onSync={handleSyncCdn}
|
||||
isSyncing={isSyncing}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
{(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" />
|
||||
<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>
|
||||
|
||||
<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>
|
||||
)}
|
||||
</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>
|
||||
|
||||
{/* Right Column: Archives */}
|
||||
<div className="lg:col-span-1">
|
||||
<ComponentCard
|
||||
title="Version Archives"
|
||||
desc="Browse historical versions and promote them back to Latest if needed."
|
||||
>
|
||||
<ArchiveManagementTable
|
||||
certificates={certificates}
|
||||
onPromote={handlePromote}
|
||||
isPromoting={isPromoting}
|
||||
/>
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user