+
- {(isLoading || isRenewing) && (
-
-
+
+ c.is_latest || certificates.length === 1)}
+ onRenew={setConfirmRenewUuid}
+ isRenewing={isRenewing}
+ />
+
+ {(isLoading || isRenewing) && (
+
+ )}
+
+
+
+
{t("info_title")}
+
+ - {t("info_point_1")}
+ - {t("info_point_2")}
+ - {t("info_point_3")}
+ - {t("info_point_4")}
+
- )}
-
-
-
-
{t("info_title")}
-
- - {t("info_point_1")}
- - {t("info_point_2")}
- - {t("info_point_3")}
- - {t("info_point_4")}
-
+
+
+ {/* Right Column: Archives */}
+
diff --git a/src/components/admin/ArchiveManagementTable.tsx b/src/components/admin/ArchiveManagementTable.tsx
new file mode 100644
index 0000000..f2d844c
--- /dev/null
+++ b/src/components/admin/ArchiveManagementTable.tsx
@@ -0,0 +1,154 @@
+"use client";
+
+import React, { useState, useMemo } from "react";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from "../ui/table";
+import Badge from "../ui/badge/Badge";
+import Button from "../ui/button/Button";
+import InputField from "../form/input/InputField";
+import { useTranslations } from "next-intl";
+
+interface CaCertificate {
+ uuid: string;
+ ca_type: string;
+ common_name: string;
+ serial_number: string;
+ valid_from: string;
+ valid_to: string;
+ status: string;
+ is_latest: boolean;
+}
+
+interface ArchiveManagementTableProps {
+ certificates: CaCertificate[];
+ onPromote: (uuid: string) => void;
+ isPromoting: boolean;
+}
+
+export default function ArchiveManagementTable({
+ certificates,
+ onPromote,
+ isPromoting,
+}: ArchiveManagementTableProps) {
+ const t = useTranslations("RootCA");
+ const [searchTerm, setSearchTerm] = useState("");
+
+ const formatType = (type: string) => {
+ return type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
+ };
+
+ const filteredCertificates = useMemo(() => {
+ return certificates.filter((cert) => {
+ const search = searchTerm.toLowerCase();
+ return (
+ cert.common_name.toLowerCase().includes(search) ||
+ cert.ca_type.toLowerCase().includes(search) ||
+ cert.serial_number.toLowerCase().includes(search) ||
+ cert.uuid.toLowerCase().includes(search)
+ );
+ });
+ }, [certificates, searchTerm]);
+
+ return (
+
+ {/* Table Header Controls */}
+
+
CDN Archive History
+
+ setSearchTerm(e.target.value)}
+ className="!py-2"
+ />
+
+
+
+
+
+
+
+
+
+ Version (UUID)
+
+
+ {t("common_name_th")}
+
+
+ {t("validity_th")}
+
+
+ Status
+
+
+ {t("actions_th")}
+
+
+
+
+
+ {filteredCertificates.map((cert) => (
+
+
+ {cert.uuid}
+
+
+
+ {cert.common_name}
+ {formatType(cert.ca_type)}
+
+
+
+
+ {new Date(cert.valid_from).toLocaleDateString()}
+ {t("validity_to_label")}
+ {new Date(cert.valid_to).toLocaleDateString()}
+
+
+
+
+
+ {cert.status === "valid" ? t("status_valid") : t("status_expired")}
+
+ {cert.is_latest && (
+ LIVE - LATEST
+ )}
+
+
+
+ {!cert.is_latest && (
+
+ )}
+ {cert.is_latest && (
+ Currently Public
+ )}
+
+
+ ))}
+ {filteredCertificates.length === 0 && (
+
+
+ No versions found.
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/components/admin/CdnManagementCard.tsx b/src/components/admin/CdnManagementCard.tsx
new file mode 100644
index 0000000..469cce7
--- /dev/null
+++ b/src/components/admin/CdnManagementCard.tsx
@@ -0,0 +1,96 @@
+"use client";
+
+import React from "react";
+import ComponentCard from "@/components/common/ComponentCard";
+import { useTranslations } from "next-intl";
+
+interface CdnManagementCardProps {
+ onSync: (type: "all" | "crt" | "installers" | "bundles", mode: "latest" | "archive" | "both") => void;
+ isSyncing: boolean;
+ disabled: boolean;
+}
+
+export default function CdnManagementCard({ onSync, isSyncing, disabled }: CdnManagementCardProps) {
+ const t = useTranslations("RootCA");
+
+ const SyncButton = ({
+ onClick,
+ label,
+ desc,
+ variant = "blue"
+ }: {
+ onClick: () => void;
+ label: string;
+ desc: string;
+ variant?: "blue" | "purple" | "indigo" | "gray"
+ }) => {
+ const variantClasses = {
+ blue: "bg-blue-600 hover:bg-blue-700 disabled:bg-blue-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",
+ gray: "bg-gray-600 hover:bg-gray-700 disabled:bg-gray-400",
+ };
+
+ return (
+
+
+
+
+
+
+ );
+ };
+
+ return (
+
+
+ onSync("all", "both")}
+ variant="indigo"
+ />
+ onSync("all", "latest")}
+ variant="blue"
+ />
+ onSync("all", "archive")}
+ variant="purple"
+ />
+ onSync("bundles", "latest")}
+ variant="gray"
+ />
+
+
+ );
+}
diff --git a/src/messages/en.json b/src/messages/en.json
index 1cd424b..c9b46a3 100644
--- a/src/messages/en.json
+++ b/src/messages/en.json
@@ -658,7 +658,7 @@
"validity_th": "Validity Period",
"status_th": "Status",
"actions_th": "Actions",
- "to": "to",
+ "validity_to_label": "to",
"status_valid": "Valid",
"status_expired": "Expired",
"renew_button": "Renew (10Y)",
diff --git a/src/messages/id.json b/src/messages/id.json
index 52df9d1..6fffd10 100644
--- a/src/messages/id.json
+++ b/src/messages/id.json
@@ -658,7 +658,7 @@
"validity_th": "Masa Berlaku",
"status_th": "Status",
"actions_th": "Aksi",
- "to": "ke",
+ "validity_to_label": "ke",
"status_valid": "Valid",
"status_expired": "Kedaluwarsa",
"renew_button": "Perbarui (10T)",