diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index b86f0994..7cd46980 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -1,7 +1,7 @@ { "access.page.title": "Authorization", - "access.nodata": "Please add authorization to start deploying certificate.", + "access.nodata": "No accesses. Please create an authorization first.", "access.action.add": "Create Authorization", "access.action.edit": "Edit Authorization", diff --git a/ui/src/i18n/locales/en/nls.certificate.json b/ui/src/i18n/locales/en/nls.certificate.json index bbaf3786..fc80b7bd 100644 --- a/ui/src/i18n/locales/en/nls.certificate.json +++ b/ui/src/i18n/locales/en/nls.certificate.json @@ -1,7 +1,7 @@ { "certificate.page.title": "Certificates", - "certificate.nodata": "No certificates yet, create a workflow to generate certificates! 😀", + "certificate.nodata": "No certificates. Please create a workflow to generate certificates! 😀", "certificate.action.view": "View Certificate", "certificate.action.download": "Download Certificate", diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json index ba27b49e..6307e434 100644 --- a/ui/src/i18n/locales/en/nls.workflow.json +++ b/ui/src/i18n/locales/en/nls.workflow.json @@ -1,8 +1,10 @@ { "workflow.page.title": "Workflows", + + "workflow.nodata": "No workflows. Please create a workflow to generate certificates! 😀", + "workflow.detail.title": "Workflow", "workflow.detail.history": "History", - "workflow.detail.action.save": "Save updates", "workflow.detail.action.save.failed": "Save failed", "workflow.detail.action.save.failed.uncompleted": "Save failed, please complete all node settings", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 3ddd460f..6c33bf52 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -1,7 +1,7 @@ { "access.page.title": "授权管理", - "access.nodata": "请添加授权开始部署证书吧。", + "access.nodata": "暂无授权信息,请先创建。", "access.action.add": "新建授权", "access.action.edit": "编辑授权", diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json index 297a84a9..b4bb60de 100644 --- a/ui/src/i18n/locales/zh/nls.certificate.json +++ b/ui/src/i18n/locales/zh/nls.certificate.json @@ -1,7 +1,7 @@ { "certificate.page.title": "证书管理", - "certificate.nodata": "暂无证书,创建一个工作流去生成证书吧 😀", + "certificate.nodata": "暂无证书,创建一个工作流去生成证书吧~ 😀", "certificate.action.view": "查看证书", "certificate.action.download": "下载证书", diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json index 915d628e..99f4fc71 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.json +++ b/ui/src/i18n/locales/zh/nls.workflow.json @@ -1,8 +1,12 @@ { "workflow.page.title": "工作流", + + "certificate.nodata": "暂无证书,创建一个工作流去生成证书吧~ 😀", + + "workflow.nodata": "No workflows yet. Try to create a workflow to generate certificates! 😀", + "workflow.detail.title": "流程", "workflow.detail.history": "历史", - "workflow.detail.action.save": "保存变更", "workflow.detail.action.save.failed": "保存失败", "workflow.detail.action.save.failed.uncompleted": "保存失败,请完成所有节点设置", diff --git a/ui/src/pages/ConsoleLayout.tsx b/ui/src/pages/ConsoleLayout.tsx index 3d96be9a..b385ac73 100644 --- a/ui/src/pages/ConsoleLayout.tsx +++ b/ui/src/pages/ConsoleLayout.tsx @@ -60,7 +60,6 @@ const ConsoleLayout = () => { const item = menuItems.find((item) => item!.key === location.pathname) ?? menuItems.find((item) => item!.key !== "/" && location.pathname.startsWith(item!.key as string)); - console.log(item); if (item) { setMenuSelectedKey(item.key as string); } else { diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index dfc56d34..75b12ecc 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Avatar, Button, Modal, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd"; +import { Avatar, Button, Empty, Modal, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd"; import { PageHeader } from "@ant-design/pro-components"; import { Copy as CopyIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react"; import moment from "moment"; @@ -13,6 +13,9 @@ import { useConfigContext } from "@/providers/config"; const AccessList = () => { const { t } = useTranslation(); + // a flag to fix the twice-rendering issue in strict mode + const mountRef = useRef(true); + const [modalApi, ModelContextHolder] = Modal.useModal(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); @@ -111,7 +114,7 @@ const AccessList = () => { const configContext = useConfigContext(); - const fetchTableData = async () => { + const fetchTableData = useCallback(async () => { if (loading) return; setLoading(true); @@ -124,14 +127,20 @@ const AccessList = () => { setTableTotal(configContext.config.accesses.length); } catch (err) { console.error(err); + notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)} }); } finally { setLoading(false); } - }; + }, [page, pageSize, configContext.config.accesses]); useEffect(() => { + if (mountRef.current) { + mountRef.current = false; + return; + } + fetchTableData(); - }, [page, pageSize, configContext.config.accesses]); + }, [fetchTableData]); const handleDeleteClick = async (data: AccessType) => { modalApi.confirm({ @@ -143,13 +152,13 @@ const AccessList = () => { const res = await removeAccess(data); configContext.deleteAccess(res.id); } catch (err) { + console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)} }); } }, }); }; - // TODO: Empty 样式 // TODO: 响应式表格 return ( @@ -176,6 +185,9 @@ const AccessList = () => { columns={tableColumns} dataSource={tableData} loading={loading} + locale={{ + emptyText: , + }} pagination={{ current: page, pageSize: pageSize, diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index 7a927930..fccb1be0 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, 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 { Button, Empty, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd"; import { PageHeader } from "@ant-design/pro-components"; import { Eye as EyeIcon } from "lucide-react"; import moment from "moment"; @@ -17,6 +17,11 @@ const CertificateList = () => { const { t } = useTranslation(); + // a flag to fix the twice-rendering issue in strict mode + const mountRef = useRef(true); + + const [notificationApi, NotificationContextHolder] = notification.useNotification(); + const [loading, setLoading] = useState(false); const tableColumns: TableProps["columns"] = [ @@ -120,7 +125,7 @@ const CertificateList = () => { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); - const fetchTableData = async () => { + const fetchTableData = useCallback(async () => { if (loading) return; setLoading(true); @@ -137,14 +142,20 @@ const CertificateList = () => { setTableTotal(resp.totalItems); } catch (err) { console.error(err); + notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)} }); } finally { setLoading(false); } - }; + }, [page, pageSize]); useEffect(() => { + if (mountRef.current) { + mountRef.current = false; + return; + } + fetchTableData(); - }, [page, pageSize]); + }, [fetchTableData]); const [drawerOpen, setDrawerOpen] = useState(false); const [currentRecord, setCurrentRecord] = useState(); @@ -154,17 +165,21 @@ const CertificateList = () => { setCurrentRecord(certificate); }; - // TODO: Empty 样式 // TODO: 响应式表格 return ( <> + {NotificationContextHolder} + columns={tableColumns} dataSource={tableData} loading={loading} + locale={{ + emptyText: , + }} pagination={{ current: page, pageSize: pageSize, diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index d3546cd6..8449642e 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { Button, Modal, notification, Space, Switch, Table, Tooltip, Typography, type TableProps } from "antd"; +import { Button, Empty, Modal, notification, Space, Switch, Table, Tooltip, Typography, type TableProps } from "antd"; import { PageHeader } from "@ant-design/pro-components"; import { Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react"; import moment from "moment"; @@ -15,6 +15,9 @@ const WorkflowList = () => { const { t } = useTranslation(); + // a flag to fix the twice-rendering issue in strict mode + const mountRef = useRef(true); + const [modalApi, ModelContextHolder] = Modal.useModal(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); @@ -137,7 +140,7 @@ const WorkflowList = () => { const [pageSize, setPageSize] = useState(10); // TODO: 表头筛选 - const fetchTableData = async () => { + const fetchTableData = useCallback(async () => { if (loading) return; setLoading(true); @@ -154,14 +157,20 @@ const WorkflowList = () => { setTableTotal(resp.totalItems); } catch (err) { console.error(err); + notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)} }); } finally { setLoading(false); } - }; + }, [searchParams, page, pageSize]); useEffect(() => { + if (mountRef.current) { + mountRef.current = false; + return; + } + fetchTableData(); - }, [page, pageSize]); + }, [fetchTableData]); const handleEnabledChange = async (workflow: WorkflowType) => { try { @@ -180,6 +189,7 @@ const WorkflowList = () => { }); } } catch (err) { + console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)} }); } }; @@ -195,6 +205,7 @@ const WorkflowList = () => { setTableData((prev) => prev.filter((item) => item.id !== workflow.id)); } } catch (err) { + console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)} }); } }, @@ -205,7 +216,6 @@ const WorkflowList = () => { navigate("/workflows/detail"); }; - // TODO: Empty 样式 // TODO: 响应式表格 return ( @@ -233,6 +243,9 @@ const WorkflowList = () => { columns={tableColumns} dataSource={tableData} loading={loading} + locale={{ + emptyText: , + }} pagination={{ current: page, pageSize: pageSize, diff --git a/ui/src/repository/access.ts b/ui/src/repository/access.ts index 8586c04d..e522a1cd 100644 --- a/ui/src/repository/access.ts +++ b/ui/src/repository/access.ts @@ -10,14 +10,15 @@ export const list = async () => { }); }; -export const save = async (data: Access) => { - if (data.id) { - return await getPocketBase().collection("access").update(data.id, data); +export const save = async (record: Access) => { + if (record.id) { + return await getPocketBase().collection("access").update(record.id, record); } - return await getPocketBase().collection("access").create(data); + + return await getPocketBase().collection("access").create(record); }; -export const remove = async (data: Access) => { - data.deleted = moment.utc().format("YYYY-MM-DD HH:mm:ss"); - return await getPocketBase().collection("access").update(data.id, data); +export const remove = async (record: Access) => { + record.deleted = moment.utc().format("YYYY-MM-DD HH:mm:ss"); + return await getPocketBase().collection("access").update(record.id, record); }; diff --git a/ui/src/repository/deployment.ts b/ui/src/repository/deployment.ts deleted file mode 100644 index 277d0f48..00000000 --- a/ui/src/repository/deployment.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Deployment, DeploymentListReq } from "@/domain/deployment"; -import { getPocketBase } from "./pocketbase"; - -export const list = async (req: DeploymentListReq) => { - let page = 1; - if (req.page) { - page = req.page; - } - - let perPage = 50; - if (req.perPage) { - perPage = req.perPage; - } - - let filter = "domain!=null"; - if (req.domain) { - filter = `domain="${req.domain}"`; - } - - return await getPocketBase().collection("deployments").getList(page, perPage, { - filter: filter, - sort: "-deployedAt", - expand: "domain", - }); -}; diff --git a/ui/src/repository/domains.ts b/ui/src/repository/domains.ts deleted file mode 100644 index 7940f35d..00000000 --- a/ui/src/repository/domains.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { getTimeAfter } from "@/lib/time"; -import { Domain } from "@/domain/domain"; -import { getPocketBase } from "./pocketbase"; - -type DomainListReq = { - domain?: string; - page?: number; - perPage?: number; - state?: string; -}; - -export const list = async (req: DomainListReq) => { - const pb = getPocketBase(); - - let page = 1; - if (req.page) { - page = req.page; - } - - let perPage = 2; - if (req.perPage) { - perPage = req.perPage; - } - - let filter = ""; - if (req.state === "enabled") { - filter = "enabled=true"; - } else if (req.state === "disabled") { - filter = "enabled=false"; - } else if (req.state === "expired") { - filter = pb.filter("expiredAt<{:expiredAt}", { - expiredAt: getTimeAfter(15), - }); - } - - const response = pb.collection("domains").getList(page, perPage, { - sort: "-created", - expand: "lastDeployment", - filter: filter, - }); - - return response; -}; - -export const get = async (id: string) => { - const response = await getPocketBase().collection("domains").getOne(id); - return response; -}; - -export const save = async (data: Domain) => { - if (data.id) { - return await getPocketBase().collection("domains").update(data.id, data); - } - return await getPocketBase().collection("domains").create(data); -}; - -export const remove = async (id: string) => { - return await getPocketBase().collection("domains").delete(id); -}; - -type Callback = (data: Domain) => void; -export const subscribeId = (id: string, callback: Callback) => { - return getPocketBase() - .collection("domains") - .subscribe( - id, - (e) => { - if (e.action === "update") { - callback(e.record); - } - }, - { - expand: "lastDeployment", - } - ); -}; - -export const unsubscribeId = (id: string) => { - getPocketBase().collection("domains").unsubscribe(id); -}; diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts index d302d9e2..a74c6cb4 100644 --- a/ui/src/repository/workflow.ts +++ b/ui/src/repository/workflow.ts @@ -27,18 +27,18 @@ export const get = async (id: string) => { return await getPocketBase().collection("workflow").getOne(id); }; -export const save = async (data: Record) => { - if (data.id) { +export const save = async (record: Record) => { + if (record.id) { return await getPocketBase() .collection("workflow") - .update(data.id as string, data); + .update(record.id as string, record); } - return await getPocketBase().collection("workflow").create(data); + return await getPocketBase().collection("workflow").create(record); }; -export const remove = async (workflow: Workflow) => { - return await getPocketBase().collection("workflow").delete(workflow.id); +export const remove = async (record: Workflow) => { + return await getPocketBase().collection("workflow").delete(record.id); }; type WorkflowLogsReq = {