diff --git a/ui/src/components/access/AccessEditDrawer.tsx b/ui/src/components/access/AccessEditDrawer.tsx
index d4a7a4b59..9eba19a97 100644
--- a/ui/src/components/access/AccessEditDrawer.tsx
+++ b/ui/src/components/access/AccessEditDrawer.tsx
@@ -51,7 +51,7 @@ const AccessEditDrawer = ({ data, loading, trigger, scene, usage, afterSubmit, .
try {
let values: AccessModel = formRef.current!.getFieldsValue();
- if (scene === "add") {
+ if (scene === "create") {
if (data?.id) {
throw "Invalid props: `data`";
}
@@ -109,7 +109,7 @@ const AccessEditDrawer = ({ data, loading, trigger, scene, usage, afterSubmit, .
width={720}
onClose={() => setOpen(false)}
>
-
+
>
);
diff --git a/ui/src/components/access/AccessEditModal.tsx b/ui/src/components/access/AccessEditModal.tsx
index a20d50b97..dab018dd4 100644
--- a/ui/src/components/access/AccessEditModal.tsx
+++ b/ui/src/components/access/AccessEditModal.tsx
@@ -51,7 +51,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, ..
try {
let values: AccessModel = formRef.current!.getFieldsValue();
- if (scene === "add") {
+ if (scene === "create") {
if (data?.id) {
throw "Invalid props: `data`";
}
@@ -113,7 +113,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, ..
onCancel={handleCancelClick}
>
>
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index ba9e1318f..0a7bb35b1 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -86,7 +86,7 @@ import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig";
type AccessFormFieldValues = Partial
>;
-type AccessFormScenes = "add" | "edit";
+type AccessFormScenes = "create" | "edit";
type AccessFormUsages = "both-dns-hosting" | "ca-only" | "notification-only";
export type AccessFormProps = {
@@ -406,7 +406,7 @@ const AccessForm = forwardRef(({ className,
{t("workflow_node.apply.form.provider_access.button")}
@@ -408,7 +408,7 @@ const ApplyNodeConfigForm = forwardRef
{t("workflow_node.apply.form.ca_provider_access.button")}
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
index 27a72600e..f78fb5995 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
@@ -489,7 +489,7 @@ const DeployNodeConfigForm = forwardRef
{t("workflow_node.deploy.form.provider_access.button")}
diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx
index 1bfd2b43d..da9f62daa 100644
--- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx
@@ -199,7 +199,7 @@ const NotifyNodeConfigForm = forwardRef
{t("workflow_node.notify.form.provider_access.button")}
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index 9db5b047c..db735bb55 100644
--- a/ui/src/i18n/locales/en/nls.access.json
+++ b/ui/src/i18n/locales/en/nls.access.json
@@ -2,7 +2,10 @@
"access.page.title": "Credentials",
"access.page.subtitle": "Credentials store authentication information (username and password, API key, OAuth tokens, etc.) to connect with specific third-party apps and services.",
- "access.nodata": "No credentials. Please create an credential first.",
+ "access.nodata": "No credentials",
+ "access.nodata.title": "No credentials",
+ "access.nodata.description": "Add a credential to store authentication information.",
+ "access.nodata.button": "Create credential",
"access.search.placeholder": "Search by credential name ...",
@@ -13,7 +16,6 @@
"access.action.delete.confirm": "Are you sure want to delete this \"{{name}}\" credential?
This action cannot be undone.",
"access.props.name": "Name",
- "access.props.provider": "Provider",
"access.props.provider.usage.dns": "DNS provider",
"access.props.provider.usage.hosting": "Hosting provider",
"access.props.provider.usage.ca": "Certificate authority",
diff --git a/ui/src/i18n/locales/en/nls.certificate.json b/ui/src/i18n/locales/en/nls.certificate.json
index fc654faec..d8ba23d41 100644
--- a/ui/src/i18n/locales/en/nls.certificate.json
+++ b/ui/src/i18n/locales/en/nls.certificate.json
@@ -3,8 +3,8 @@
"certificate.page.subtitle": "SSL certificates contain the website's public key and the website's identity, along with related information. They are generated from the execution output of workflows.",
"certificate.nodata.title": "No Certificates",
- "certificate.nodata.description": "Create a workflow to generate certificates!",
- "certificate.nodata.button": "Create workflow",
+ "certificate.nodata.description": "Add a workflow to generate certificates!",
+ "certificate.nodata.button": "Go to workflows",
"certificate.search.placeholder": "Search by certificate name or serial number ...",
@@ -19,9 +19,9 @@
"certificate.props.validity.less_than_a_day": "Less than 1 day",
"certificate.props.validity.expired": "Expired",
"certificate.props.validity.expiration": "Expire on {{date}}",
- "certificate.props.validity.filters.all": "All",
- "certificate.props.validity.filters.expire_soon": "Expire soon",
- "certificate.props.validity.filters.expired": "Expired",
+ "certificate.props.validity.filter.all": "All",
+ "certificate.props.validity.filter.expire_soon": "Expire soon",
+ "certificate.props.validity.filter.expired": "Expired",
"certificate.props.brand": "Brand",
"certificate.props.source": "Source",
"certificate.props.source.workflow": "Workflow",
diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json
index 304e3b69e..c8baf5067 100644
--- a/ui/src/i18n/locales/en/nls.workflow.json
+++ b/ui/src/i18n/locales/en/nls.workflow.json
@@ -23,8 +23,8 @@
"workflow.props.trigger.manual": "Manual",
"workflow.props.last_run_at": "Last run at",
"workflow.props.state": "State",
- "workflow.props.state.filters.enabled": "Active",
- "workflow.props.state.filters.disabled": "Inactive",
+ "workflow.props.state.filter.enabled": "Active",
+ "workflow.props.state.filter.disabled": "Inactive",
"workflow.props.created_at": "Created at",
"workflow.props.updated_at": "Updated at",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index e0c933d0d..edb09fce0 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -2,7 +2,9 @@
"access.page.title": "授权管理",
"access.page.subtitle": "授权中存储有用于访问特定第三方应用程序和服务的身份验证信息(如账号密码、API 密钥、OAuth 令牌等)。",
- "access.nodata": "暂无授权信息,请先新建授权",
+ "access.nodata.title": "暂无授权",
+ "access.nodata.description": "新建一个授权并存储身份验证信息吧~",
+ "access.nodata.button": "新建授权",
"access.search.placeholder": "按授权名称搜索……",
@@ -13,7 +15,6 @@
"access.action.delete.confirm": "确定要删除授权「{{name}}」吗?
注意此操作不可撤销,请谨慎操作。",
"access.props.name": "名称",
- "access.props.provider": "提供商",
"access.props.provider.usage.dns": "DNS 提供商",
"access.props.provider.usage.hosting": "主机提供商",
"access.props.provider.usage.ca": "证书颁发机构",
diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json
index f89028ad1..78569f948 100644
--- a/ui/src/i18n/locales/zh/nls.certificate.json
+++ b/ui/src/i18n/locales/zh/nls.certificate.json
@@ -4,7 +4,7 @@
"certificate.nodata.title": "暂无证书",
"certificate.nodata.description": "新建一个工作流去生成证书吧~",
- "certificate.nodata.button": "新建工作流",
+ "certificate.nodata.button": "前往工作流",
"certificate.search.placeholder": "按证书名称或序列号搜索……",
@@ -19,9 +19,9 @@
"certificate.props.validity.less_than_a_day": "不足 1 天",
"certificate.props.validity.expired": "已过期",
"certificate.props.validity.expiration": "{{date}} 过期",
- "certificate.props.validity.filters.all": "全部",
- "certificate.props.validity.filters.expire_soon": "即将过期",
- "certificate.props.validity.filters.expired": "已过期",
+ "certificate.props.validity.filter.all": "全部",
+ "certificate.props.validity.filter.expire_soon": "即将过期",
+ "certificate.props.validity.filter.expired": "已过期",
"certificate.props.brand": "证书品牌",
"certificate.props.source": "来源",
"certificate.props.source.workflow": "工作流",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json
index c13f3dcab..bcf823bb8 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.json
@@ -23,8 +23,8 @@
"workflow.props.trigger.manual": "手动",
"workflow.props.last_run_at": "最近执行时间",
"workflow.props.state": "启用状态",
- "workflow.props.state.filters.enabled": "启用",
- "workflow.props.state.filters.disabled": "未启用",
+ "workflow.props.state.filter.enabled": "启用",
+ "workflow.props.state.filter.disabled": "未启用",
"workflow.props.created_at": "创建时间",
"workflow.props.updated_at": "更新时间",
diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx
index 7ce89c523..b01b583ef 100644
--- a/ui/src/pages/accesses/AccessList.tsx
+++ b/ui/src/pages/accesses/AccessList.tsx
@@ -1,13 +1,14 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
-import { IconCopy, IconEdit, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
+import { IconCirclePlus, IconCopy, IconEdit, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
import { useRequest } from "ahooks";
-import { App, Avatar, Button, Empty, Input, Table, type TableProps, Tabs, Tooltip, Typography } from "antd";
+import { App, Avatar, Button, Input, Skeleton, Table, type TableProps, Tabs, Tooltip, Typography } from "antd";
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
import AccessEditDrawer, { type AccessEditDrawerProps } from "@/components/access/AccessEditDrawer";
+import Empty from "@/components/Empty";
import { type AccessModel } from "@/domain/access";
import { ACCESS_USAGES, accessProvidersMap } from "@/domain/provider";
import { useZustandShallowSelector } from "@/hooks";
@@ -48,17 +49,16 @@ const AccessList = () => {
key: "name",
title: t("access.props.name"),
ellipsis: true,
- render: (_, record) => <>{record.name}>,
- },
- {
- key: "provider",
- title: t("access.props.provider"),
- ellipsis: true,
render: (_, record) => {
return (
-
-
-
{t(accessProvidersMap.get(record.provider)?.name ?? "")}
+
+
+
+ {record.name}
+
+ {t(accessProvidersMap.get(record.provider)?.name ?? "")}
+
+
);
},
@@ -71,14 +71,6 @@ const AccessList = () => {
return dayjs(record.created!).format("YYYY-MM-DD HH:mm:ss");
},
},
- {
- key: "updatedAt",
- title: t("access.props.updated_at"),
- ellipsis: true,
- render: (_, record) => {
- return dayjs(record.updated!).format("YYYY-MM-DD HH:mm:ss");
- },
- },
{
key: "$action",
align: "end",
@@ -86,35 +78,36 @@ const AccessList = () => {
width: 120,
render: (_, record) => (
-
- } variant="text" />
-
- }
- />
-
-
- } variant="text" />
-
- }
- />
-
+
+ }
+ variant="text"
+ onClick={(e) => {
+ e.stopPropagation();
+ handleRecordDetailClick(record);
+ }}
+ />
+
+
+ }
+ variant="text"
+ onClick={(e) => {
+ e.stopPropagation();
+ handleRecordDuplicateClick(record);
+ }}
+ />
+
}
variant="text"
- onClick={() => {
- handleDeleteClick(record);
+ onClick={(e) => {
+ e.stopPropagation();
+ handleRecordDeleteClick(record);
}}
/>
@@ -190,7 +183,23 @@ const AccessList = () => {
fetchAccesses();
};
- const handleDeleteClick = async (access: AccessModel) => {
+ const [detailRecord, setDetailRecord] = useState>();
+ const [detailScene, setDetailScene] = useState("create");
+ const [detailOpen, setDetailOpen] = useState(false);
+
+ const handleRecordDetailClick = (access: AccessModel) => {
+ setDetailRecord(access);
+ setDetailScene("edit");
+ setDetailOpen(true);
+ };
+
+ const handleRecordDuplicateClick = (access: AccessModel) => {
+ setDetailRecord({ ...access, id: undefined, name: `${access.name}-copy` });
+ setDetailScene("create");
+ setDetailOpen(true);
+ };
+
+ const handleRecordDeleteClick = async (access: AccessModel) => {
modal.confirm({
title: {t("access.action.delete")},
content: ,
@@ -238,8 +247,8 @@ const AccessList = () => {
} size="large" type="primary">
{t("access.action.create")}
@@ -269,12 +278,32 @@ const AccessList = () => {
size="large"
onChange={(key) => handleTabChange(key)}
/>
+
columns={tableColumns}
dataSource={tableData}
loading={!loadedAtOnce || loading}
locale={{
- emptyText: ,
+ emptyText: loading ? (
+
+ ) : (
+ }
+ extra={
+ } type="primary">
+ {t("access.action.create")}
+
+ }
+ />
+ }
+ />
+ ),
}}
pagination={{
current: page,
@@ -290,9 +319,17 @@ const AccessList = () => {
setPageSize(pageSize);
},
}}
+ rowClassName="cursor-pointer"
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
+ onRow={(record) => ({
+ onClick: () => {
+ handleRecordDetailClick(record);
+ },
+ })}
/>
+
+
);
diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx
index ace2e0d34..aebc892c6 100644
--- a/ui/src/pages/certificates/CertificateList.tsx
+++ b/ui/src/pages/certificates/CertificateList.tsx
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom";
-import { IconBrowserShare, IconCirclePlus, IconReload, IconShieldCheckeredFilled, IconTrash } from "@tabler/icons-react";
+import { IconBrowserShare, IconExternalLink, IconReload, IconShieldCheckeredFilled, IconTrash } from "@tabler/icons-react";
import { useRequest } from "ahooks";
import { App, Button, Input, Segmented, Skeleton, Table, type TableProps, Tooltip, Typography } from "antd";
import dayjs from "dayjs";
@@ -34,6 +34,13 @@ const CertificateList = () => {
const [pageSize, setPageSize] = useState(() => parseInt(+searchParams.get("perPage")! + "") || 15);
const tableColumns: TableProps["columns"] = [
+ {
+ key: "$index",
+ align: "center",
+ fixed: "left",
+ width: 50,
+ render: (_, __, index) => (page - 1) * pageSize + index + 1,
+ },
{
key: "name",
title: t("certificate.props.subject_alt_names"),
@@ -209,8 +216,8 @@ const CertificateList = () => {
const [detailRecord, setDetailRecord] = useState();
const [detailOpen, setDetailOpen] = useState(false);
- const handleRecordDetailClick = (record: CertificateModel) => {
- setDetailRecord(record);
+ const handleRecordDetailClick = (certificate: CertificateModel) => {
+ setDetailRecord(certificate);
setDetailOpen(true);
};
@@ -252,9 +259,9 @@ const CertificateList = () => {
{t("certificate.props.validity.filters.all")}, value: "" },
- { label: {t("certificate.props.validity.filters.expire_soon")}, value: "expireSoon" },
- { label: {t("certificate.props.validity.filters.expired")}, value: "expired" },
+ { label: {t("certificate.props.validity.filter.all")}, value: "" },
+ { label: {t("certificate.props.validity.filter.expire_soon")}, value: "expireSoon" },
+ { label: {t("certificate.props.validity.filter.expired")}, value: "expired" },
]}
size="large"
value={(filters["state"] as string) || ""}
@@ -300,8 +307,8 @@ const CertificateList = () => {
{t("common.button.reload")}
) : (
- } type="primary" onClick={() => navigate("/workflows/new")}>
- {t("workflow.action.create")}
+ } type="primary" onClick={() => navigate("/workflows")}>
+ {t("certificate.nodata.button")}
)
}
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index c92cacca2..4007c0180 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -77,8 +77,8 @@ const WorkflowList = () => {
defaultFilteredValue: searchParams.has("state") ? [searchParams.get("state") as string] : undefined,
filterDropdown: ({ setSelectedKeys, confirm, clearFilters }) => {
const items: Required["items"] = [
- ["enabled", "workflow.props.state.filters.enabled"],
- ["disabled", "workflow.props.state.filters.disabled"],
+ ["enabled", "workflow.props.state.filter.enabled"],
+ ["disabled", "workflow.props.state.filter.disabled"],
].map(([key, label]) => {
return {
key,