+
+ {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" },
+ ]}
+ size="large"
+ value={(filters["state"] as string) || ""}
+ onChange={(value) => {
+ setPage(1);
+ setFilters((prev) => ({ ...prev, state: value }));
+ }}
+ />
+
{
dataSource={tableData}
loading={loading}
locale={{
- emptyText: ,
+ emptyText: loading ? (
+
+ ) : (
+ }
+ extra={
+ loadedError ? (
+ } type="primary" onClick={handleReloadClick}>
+ {t("common.button.reload")}
+
+ ) : (
+ } type="primary" onClick={() => navigate("/workflows/new")}>
+ {t("workflow.action.create")}
+
+ )
+ }
+ />
+ ),
}}
pagination={{
current: page,
@@ -320,9 +322,20 @@ const CertificateList = () => {
setPageSize(pageSize);
},
}}
+ rowClassName="cursor-pointer"
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
+ onChange={(_, __, sorter) => {
+ setSorter(Array.isArray(sorter) ? sorter[0] : sorter);
+ }}
+ onRow={(record) => ({
+ onClick: () => {
+ handleRecordDetailClick(record);
+ },
+ })}
/>
+
+
);
diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx
index 7b99ef352..9f3a35e04 100644
--- a/ui/src/pages/dashboard/Dashboard.tsx
+++ b/ui/src/pages/dashboard/Dashboard.tsx
@@ -30,9 +30,78 @@ const Dashboard = () => {
const { t } = useTranslation();
+ const breakpoints = Grid.useBreakpoint();
+
+ return (
+
+
+
{t("dashboard.page.title")}
+
+
+
+
+
+
+
+
+ } onClick={() => navigate("/workflows/new")}>
+ {t("dashboard.quick_actions.create_workflow")}
+
+ } onClick={() => navigate("/settings/account")}>
+ {t("dashboard.quick_actions.change_login_password")}
+
+ } onClick={() => navigate("/settings/ssl-provider")}>
+ {t("dashboard.quick_actions.configure_ca")}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const StatisticCard = ({
+ label,
+ loading,
+ icon,
+ value,
+ suffix,
+ onClick,
+}: {
+ label: React.ReactNode;
+ loading?: boolean;
+ icon: React.ReactNode;
+ value?: string | number | React.ReactNode;
+ suffix?: React.ReactNode;
+ onClick?: () => void;
+}) => {
+ return (
+
+
+ {icon}
+ {
+ return {value};
+ }}
+ suffix={{suffix}}
+ />
+
+
+ );
+};
+
+const StatisticCards = () => {
+ const navigate = useNavigate();
+
+ const { t } = useTranslation();
+
const { notification } = App.useApp();
const { token: themeToken } = theme.useToken();
- const breakpoints = Grid.useBreakpoint();
const statisticsGridSpans = {
xs: { flex: "50%" },
@@ -63,6 +132,65 @@ const Dashboard = () => {
}
);
+ return (
+
+
+ }
+ label={t("dashboard.statistics.all_certificates")}
+ loading={statisticsLoading}
+ value={statistics?.certificateTotal ?? "-"}
+ suffix={t("dashboard.statistics.unit")}
+ onClick={() => navigate("/certificates")}
+ />
+
+
+ }
+ label={t("dashboard.statistics.expire_soon_certificates")}
+ loading={statisticsLoading}
+ value={statistics?.certificateExpireSoon ?? "-"}
+ suffix={t("dashboard.statistics.unit")}
+ onClick={() => navigate("/certificates?state=expireSoon")}
+ />
+
+
+ }
+ label={t("dashboard.statistics.expired_certificates")}
+ loading={statisticsLoading}
+ value={statistics?.certificateExpired ?? "-"}
+ suffix={t("dashboard.statistics.unit")}
+ onClick={() => navigate("/certificates?state=expired")}
+ />
+
+
+ }
+ label={t("dashboard.statistics.all_workflows")}
+ loading={statisticsLoading}
+ value={statistics?.workflowTotal ?? "-"}
+ suffix={t("dashboard.statistics.unit")}
+ onClick={() => navigate("/workflows")}
+ />
+
+
+ }
+ label={t("dashboard.statistics.enabled_workflows")}
+ loading={statisticsLoading}
+ value={statistics?.workflowEnabled ?? "-"}
+ suffix={t("dashboard.statistics.unit")}
+ onClick={() => navigate("/workflows?state=enabled")}
+ />
+
+
+ );
+};
+
+const WorkflowRunHistoryTable = () => {
+ const { t } = useTranslation();
+
const tableColumns: TableProps
["columns"] = [
{
key: "$index",
@@ -161,127 +289,18 @@ const Dashboard = () => {
);
return (
-
-
-
{t("dashboard.page.title")}
-
-
-
- }
- label={t("dashboard.statistics.all_certificates")}
- loading={statisticsLoading}
- value={statistics?.certificateTotal ?? "-"}
- suffix={t("dashboard.statistics.unit")}
- onClick={() => navigate("/certificates")}
- />
-
-
- }
- label={t("dashboard.statistics.expire_soon_certificates")}
- loading={statisticsLoading}
- value={statistics?.certificateExpireSoon ?? "-"}
- suffix={t("dashboard.statistics.unit")}
- onClick={() => navigate("/certificates?state=expireSoon")}
- />
-
-
- }
- label={t("dashboard.statistics.expired_certificates")}
- loading={statisticsLoading}
- value={statistics?.certificateExpired ?? "-"}
- suffix={t("dashboard.statistics.unit")}
- onClick={() => navigate("/certificates?state=expired")}
- />
-
-
- }
- label={t("dashboard.statistics.all_workflows")}
- loading={statisticsLoading}
- value={statistics?.workflowTotal ?? "-"}
- suffix={t("dashboard.statistics.unit")}
- onClick={() => navigate("/workflows")}
- />
-
-
- }
- label={t("dashboard.statistics.enabled_workflows")}
- loading={statisticsLoading}
- value={statistics?.workflowEnabled ?? "-"}
- suffix={t("dashboard.statistics.unit")}
- onClick={() => navigate("/workflows?state=enabled")}
- />
-
-
-
-
-
-
-
-
- } onClick={() => navigate("/workflows/new")}>
- {t("dashboard.quick_actions.create_workflow")}
-
- } onClick={() => navigate("/settings/account")}>
- {t("dashboard.quick_actions.change_login_password")}
-
- } onClick={() => navigate("/settings/ssl-provider")}>
- {t("dashboard.quick_actions.configure_ca")}
-
-
-
-
-
- columns={tableColumns}
- dataSource={tableData}
- loading={tableLoading}
- locale={{
- emptyText: ,
- }}
- pagination={false}
- rowKey={(record) => record.id}
- scroll={{ x: "max(100%, 720px)" }}
- size="small"
- />
-
-
-
-
- );
-};
-
-const StatisticCard = ({
- label,
- loading,
- icon,
- value,
- suffix,
- onClick,
-}: {
- label: React.ReactNode;
- loading?: boolean;
- icon: React.ReactNode;
- value?: string | number | React.ReactNode;
- suffix?: React.ReactNode;
- onClick?: () => void;
-}) => {
- return (
-
-
- {icon}
- {
- return {value};
- }}
- suffix={{suffix}}
- />
-
-
+
+ columns={tableColumns}
+ dataSource={tableData}
+ loading={tableLoading}
+ locale={{
+ emptyText: ,
+ }}
+ pagination={false}
+ rowKey={(record) => record.id}
+ scroll={{ x: "max(100%, 720px)" }}
+ size="small"
+ />
);
};
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index 30eea106d..c92cacca2 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -9,7 +9,6 @@ import { ClientResponseError } from "pocketbase";
import WorkflowStatusIcon from "@/components/workflow/WorkflowStatusIcon";
import { WORKFLOW_TRIGGERS, type WorkflowModel, cloneNode, initWorkflow, isAllNodesValidated } from "@/domain/workflow";
-import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
import { list as listWorkflows, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
import { getErrMsg } from "@/utils/error";
@@ -22,6 +21,15 @@ const WorkflowList = () => {
const { message, modal, notification } = App.useApp();
const { token: themeToken } = theme.useToken();
+ const [filters, setFilters] = useState>(() => {
+ return {
+ keyword: searchParams.get("keyword"),
+ state: searchParams.get("state"),
+ };
+ });
+ const [page, setPage] = useState(() => parseInt(+searchParams.get("page")! + "") || 1);
+ const [pageSize, setPageSize] = useState(() => parseInt(+searchParams.get("perPage")! + "") || 15);
+
const tableColumns: TableProps["columns"] = [
{
key: "$index",
@@ -69,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.filter.enabled"],
- ["disabled", "workflow.props.state.filter.disabled"],
+ ["enabled", "workflow.props.state.filters.enabled"],
+ ["disabled", "workflow.props.state.filters.disabled"],
].map(([key, label]) => {
return {
key,
@@ -120,7 +128,7 @@ const WorkflowList = () => {
{
- handleEnabledChange(record);
+ handleRecordActiveChange(record);
}}
/>
);
@@ -178,7 +186,7 @@ const WorkflowList = () => {
icon={}
variant="text"
onClick={() => {
- handleDuplicateClick(record);
+ handleRecordDuplicateClick(record);
}}
/>
@@ -190,7 +198,7 @@ const WorkflowList = () => {
icon={}
variant="text"
onClick={() => {
- handleDeleteClick(record);
+ handleRecordDeleteClick(record);
}}
/>
@@ -201,16 +209,6 @@ const WorkflowList = () => {
const [tableData, setTableData] = useState([]);
const [tableTotal, setTableTotal] = useState(0);
- const [filters, setFilters] = useState>(() => {
- return {
- keyword: searchParams.get("keyword"),
- state: searchParams.get("state"),
- };
- });
-
- const [page, setPage] = useState(() => parseInt(+searchParams.get("page")! + "") || 1);
- const [pageSize, setPageSize] = useState(() => parseInt(+searchParams.get("perPage")! + "") || 15);
-
const {
loading,
error: loadedError,
@@ -258,7 +256,7 @@ const WorkflowList = () => {
refreshData();
};
- const handleEnabledChange = async (workflow: WorkflowModel) => {
+ const handleRecordActiveChange = async (workflow: WorkflowModel) => {
try {
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
message.warning(t("workflow.action.enable.failed.uncompleted"));
@@ -285,7 +283,7 @@ const WorkflowList = () => {
}
};
- const handleDuplicateClick = (workflow: WorkflowModel) => {
+ const handleRecordDuplicateClick = (workflow: WorkflowModel) => {
modal.confirm({
title: t("workflow.action.duplicate"),
content: t("workflow.action.duplicate.confirm"),
@@ -315,7 +313,7 @@ const WorkflowList = () => {
});
};
- const handleDeleteClick = (workflow: WorkflowModel) => {
+ const handleRecordDeleteClick = (workflow: WorkflowModel) => {
modal.confirm({
title: {t("workflow.action.delete")},
content: ,
diff --git a/ui/src/repository/certificate.ts b/ui/src/repository/certificate.ts
index 486a8e210..5d7c1bb9a 100644
--- a/ui/src/repository/certificate.ts
+++ b/ui/src/repository/certificate.ts
@@ -6,6 +6,7 @@ import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase";
export type ListRequest = {
keyword?: string;
state?: "expireSoon" | "expired";
+ sort?: string;
page?: number;
perPage?: number;
};
@@ -23,12 +24,14 @@ export const list = async (request: ListRequest) => {
filters.push(pb.filter("expireAt<={:expiredAt}", { expiredAt: new Date() }));
}
+ const sort = request.sort || "-created";
+
const page = request.page || 1;
const perPage = request.perPage || 10;
return pb.collection(COLLECTION_NAME_CERTIFICATE).getList(page, perPage, {
expand: "workflowId",
filter: filters.join(" && "),
- sort: "-created",
+ sort: sort,
requestKey: null,
});
};
diff --git a/ui/types/global.utility.d.ts b/ui/types/global.utility.d.ts
index e7c8b65b8..3a227d63f 100644
--- a/ui/types/global.utility.d.ts
+++ b/ui/types/global.utility.d.ts
@@ -2,6 +2,8 @@
type Nullish = {
[P in keyof T]?: T[P] | null | undefined;
};
+
+ type ArrayElement = T extends (infer U)[] ? U : never;
}
export {};