diff --git a/ui/src/i18n/locales/en/nls.certificate.json b/ui/src/i18n/locales/en/nls.certificate.json
index 743e5efd..37bf01ef 100644
--- a/ui/src/i18n/locales/en/nls.certificate.json
+++ b/ui/src/i18n/locales/en/nls.certificate.json
@@ -9,6 +9,7 @@
"certificate.props.expiry.expired": "Expired",
"certificate.props.expiry.text.expire": "Expire",
"certificate.props.workflow": "Workflow",
+ "certificate.props.source": "Source",
"certificate.props.created": "Created",
"certificate.props.certificate": "Certificate",
"certificate.props.private.key": "Private Key",
@@ -16,4 +17,3 @@
"certificate.action.view": "View Certificate",
"certificate.action.download": "Download Certificate"
}
-
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index fd5aa42b..22051c9d 100644
--- a/ui/src/i18n/locales/en/nls.common.json
+++ b/ui/src/i18n/locales/en/nls.common.json
@@ -1,8 +1,9 @@
{
+ "common.add": "Add",
"common.save": "Save",
"common.save.succeeded.message": "Save Successful",
"common.save.failed.message": "Save Failed",
- "common.add": "Add",
+ "common.view": "View",
"common.edit": "Edit",
"common.copy": "Copy",
"common.download": "Download",
@@ -24,8 +25,9 @@
"common.text.dns": "Domain Name Server",
"common.text.dns.empty": "No DNS",
"common.text.ca": "Certificate Authority",
- "common.text.provider": "Provider",
"common.text.name": "Name",
+ "common.text.provider": "Provider",
+ "common.text.workflow": "Workflow",
"common.text.created_at": "Created At",
"common.text.updated_at": "Updated At",
"common.text.operations": "Operations",
diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json
index 79598856..249578fd 100644
--- a/ui/src/i18n/locales/zh/nls.certificate.json
+++ b/ui/src/i18n/locales/zh/nls.certificate.json
@@ -1,5 +1,5 @@
{
- "certificate.page.title": "证书",
+ "certificate.page.title": "证书管理",
"certificate.nodata": "暂无证书,添加工作流去生成证书吧😀",
@@ -9,6 +9,7 @@
"certificate.props.expiry.expired": "已到期",
"certificate.props.expiry.text.expire": "到期",
"certificate.props.workflow": "所属工作流",
+ "certificate.props.source": "来源",
"certificate.props.created": "颁发时间",
"certificate.props.certificate": "证书",
"certificate.props.private.key": "私钥",
@@ -16,4 +17,3 @@
"certificate.action.view": "查看证书",
"certificate.action.download": "下载证书"
}
-
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index 760edc0d..0d66a3ba 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -3,6 +3,7 @@
"common.save": "保存",
"common.save.succeeded.message": "保存成功",
"common.save.failed.message": "保存失败",
+ "common.view": "查看",
"common.edit": "编辑",
"common.copy": "复制",
"common.download": "下载",
@@ -26,6 +27,7 @@
"common.text.ca": "CA(证书颁发机构)",
"common.text.name": "名称",
"common.text.provider": "服务商",
+ "common.text.workflow": "工作流",
"common.text.created_at": "创建时间",
"common.text.updated_at": "更新时间",
"common.text.operations": "操作",
diff --git a/ui/src/pages/DashboardLayout.tsx b/ui/src/pages/DashboardLayout.tsx
index 74f4759c..f577bcb4 100644
--- a/ui/src/pages/DashboardLayout.tsx
+++ b/ui/src/pages/DashboardLayout.tsx
@@ -66,8 +66,8 @@ export default function Dashboard() {
{t("certificate.page.title")}
@@ -114,8 +114,8 @@ export default function Dashboard() {
{t("certificate.page.title")}
diff --git a/ui/src/pages/certificate/index.tsx b/ui/src/pages/certificate/index.tsx
deleted file mode 100644
index a9b332c5..00000000
--- a/ui/src/pages/certificate/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import CertificateList from "@/components/certificate/CertificateList";
-import { useTranslation } from "react-i18next";
-
-const Certificate = () => {
- const { t } = useTranslation();
- return (
-
-
{t("certificate.page.title")}
-
-
-
- );
-};
-
-export default Certificate;
diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx
new file mode 100644
index 00000000..fb1d67e3
--- /dev/null
+++ b/ui/src/pages/certificates/CertificateList.tsx
@@ -0,0 +1,176 @@
+import { useEffect, useState } from "react";
+import { useNavigate, useSearchParams } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+import { Button, Space, Table, Tooltip, Typography, type TableProps } from "antd";
+import { PageHeader } from "@ant-design/pro-components";
+import { Eye as EyeIcon } from "lucide-react";
+
+import { Certificate as CertificateType } from "@/domain/certificate";
+import { list as listCertificate, type CertificateListReq } from "@/repository/certificate";
+import { diffDays, getLeftDays } from "@/lib/time";
+
+const CertificateList = () => {
+ const { t } = useTranslation();
+
+ const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+
+ const [loading, setLoading] = useState(false);
+
+ const tableColumns: TableProps["columns"] = [
+ {
+ key: "$index",
+ align: "center",
+ title: "",
+ width: 50,
+ render: (_, __, index) => (page - 1) * pageSize + index + 1,
+ },
+ {
+ key: "name",
+ title: t("common.text.domain"),
+ render: (_, record) => {record.san},
+ },
+ {
+ key: "expiry",
+ title: t("certificate.props.expiry"),
+ render: (_, record) => {
+ const leftDays = getLeftDays(record.expireAt);
+ const allDays = diffDays(record.expireAt, record.created);
+ return (
+
+ {leftDays > 0 ? (
+
+ {leftDays} / {allDays} {t("certificate.props.expiry.days")}
+
+ ) : (
+ {t("certificate.props.expiry.expired")}
+ )}
+
+
+ {new Date(record.expireAt).toLocaleString().split(" ")[0]} {t("certificate.props.expiry.text.expire")}
+
+
+ );
+ },
+ },
+ {
+ key: "source",
+ title: t("certificate.props.source"),
+ render: (_, record) => {
+ const workflowId = record.workflow;
+ return workflowId ? (
+
+ {t("common.text.workflow")}
+ {
+ navigate(`/workflow/detail?id=${workflowId}`);
+ }}
+ >
+ {record.expand?.workflow?.name ?? ""}
+
+
+ ) : (
+ <>TODO: 手动上传>
+ );
+ },
+ },
+ {
+ key: "createdAt",
+ title: t("common.text.created_at"),
+ ellipsis: true,
+ render: (_, record) => {
+ return new Date(record.created!).toLocaleString();
+ },
+ },
+ {
+ key: "updatedAt",
+ title: t("common.text.updated_at"),
+ ellipsis: true,
+ render: (_, record) => {
+ return new Date(record.updated!).toLocaleString();
+ },
+ },
+ {
+ key: "$operations",
+ align: "end",
+ width: 100,
+ render: (_, record) => (
+
+
+ }
+ onClick={() => {
+ // TODO: 查看证书详情
+ alert("TODO");
+ }}
+ />
+
+
+ ),
+ },
+ ];
+ const [tableData, setTableData] = useState([]);
+ const [tableTotal, setTableTotal] = useState(0);
+
+ const [page, setPage] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+
+ const fetchTableData = async () => {
+ if (loading) return;
+ setLoading(true);
+
+ const state = searchParams.get("state");
+ const req: CertificateListReq = { page: page, perPage: pageSize };
+ if (state) {
+ req.state = state as CertificateListReq["state"];
+ }
+
+ try {
+ const resp = await listCertificate(req);
+
+ setTableData(resp.items);
+ setTableTotal(resp.totalItems);
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchTableData();
+ }, [page, pageSize]);
+
+ // TODO: Empty 样式
+
+ return (
+ <>
+
+
+
+ columns={tableColumns}
+ dataSource={tableData}
+ rowKey={(record) => record.id}
+ loading={loading}
+ pagination={{
+ current: page,
+ pageSize: pageSize,
+ total: tableTotal,
+ onChange: (page, pageSize) => {
+ setPage(page);
+ setPageSize(pageSize);
+ },
+ onShowSizeChange: (page, pageSize) => {
+ setPage(page);
+ setPageSize(pageSize);
+ },
+ }}
+ />
+ >
+ );
+};
+
+export default CertificateList;
diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx
index 8798e371..2eccd1e1 100644
--- a/ui/src/pages/dashboard/Dashboard.tsx
+++ b/ui/src/pages/dashboard/Dashboard.tsx
@@ -38,7 +38,7 @@ const Dashboard = () => {
{statistic?.certificateTotal ? (
-
+
{statistic?.certificateTotal}
) : (
@@ -59,7 +59,7 @@ const Dashboard = () => {
{statistic?.certificateExpireSoon ? (
-
+
{statistic?.certificateExpireSoon}
) : (
@@ -80,7 +80,7 @@ const Dashboard = () => {
{statistic?.certificateExpired ? (
-
+
{statistic?.certificateExpired}
) : (
diff --git a/ui/src/pages/workflow/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx
similarity index 100%
rename from ui/src/pages/workflow/WorkflowDetail.tsx
rename to ui/src/pages/workflows/WorkflowDetail.tsx
diff --git a/ui/src/pages/workflow/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
similarity index 99%
rename from ui/src/pages/workflow/WorkflowList.tsx
rename to ui/src/pages/workflows/WorkflowList.tsx
index 6451698a..14407c39 100644
--- a/ui/src/pages/workflow/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -9,17 +9,16 @@ import { Workflow as WorkflowType } from "@/domain/workflow";
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow, type WorkflowListReq } from "@/repository/workflow";
const WorkflowList = () => {
- const navigate = useNavigate();
-
const { t } = useTranslation();
+ const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+
const [modalApi, ModelContextHolder] = Modal.useModal();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
const [loading, setLoading] = useState
(false);
- const [searchParams] = useSearchParams();
-
const tableColumns: TableProps["columns"] = [
{
key: "$index",
@@ -201,6 +200,8 @@ const WorkflowList = () => {
navigate("/workflow/detail");
};
+ // TODO: Empty 样式
+
return (
<>
{
const pb = getPocketBase();
- let page = 1;
- if (req.page) {
- page = req.page;
- }
-
- let perPage = 2;
- if (req.perPage) {
- perPage = req.perPage;
- }
+ const page = req.page || 1;
+ const perPage = req.perPage || 10;
const options: RecordListOptions = {
sort: "-created",
@@ -37,7 +31,5 @@ export const list = async (req: CertificateListReq) => {
});
}
- const response = pb.collection("certificate").getList(page, perPage, options);
-
- return response;
+ return pb.collection("certificate").getList(page, perPage, options);
};
diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts
index 7a7be749..8a7d2d5e 100644
--- a/ui/src/repository/workflow.ts
+++ b/ui/src/repository/workflow.ts
@@ -10,15 +10,17 @@ export type WorkflowListReq = {
};
export const list = async (req: WorkflowListReq) => {
+ const pb = getPocketBase();
+
const page = req.page || 1;
const perPage = req.perPage || 10;
const options: RecordListOptions = { sort: "-created" };
if (req.enabled != null) {
- options.filter = getPocketBase().filter("enabled={:enabled}", { enabled: req.enabled });
+ options.filter = pb.filter("enabled={:enabled}", { enabled: req.enabled });
}
- return await getPocketBase().collection("workflow").getList(page, perPage, options);
+ return await pb.collection("workflow").getList(page, perPage, options);
};
export const get = async (id: string) => {
diff --git a/ui/src/router.tsx b/ui/src/router.tsx
index 680e028a..1897e29a 100644
--- a/ui/src/router.tsx
+++ b/ui/src/router.tsx
@@ -10,9 +10,9 @@ import Dashboard from "./pages/dashboard/Dashboard";
import Account from "./pages/setting/Account";
import Notify from "./pages/setting/Notify";
import SSLProvider from "./pages/setting/SSLProvider";
-import WorkflowList from "./pages/workflow/WorkflowList";
-import WorkflowDetail from "./pages/workflow/WorkflowDetail";
-import Certificate from "./pages/certificate";
+import WorkflowList from "./pages/workflows/WorkflowList";
+import WorkflowDetail from "./pages/workflows/WorkflowDetail";
+import CertificateList from "./pages/certificates/CertificateList";
export const router = createHashRouter([
{
@@ -32,8 +32,8 @@ export const router = createHashRouter([
element: ,
},
{
- path: "/certificate",
- element: ,
+ path: "/certificates",
+ element: ,
},
{
path: "/setting",