From 8e9e55d1bbd4b24951b3675e75643b01e02880ab Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 23 Jul 2025 10:48:18 +0800 Subject: [PATCH 01/13] fix(ui): tailwindcss safelisting with ranges --- ui/src/components/access/AccessForm.tsx | 2 ++ ui/src/components/workflow/WorkflowElement.tsx | 2 +- ui/src/global.css | 2 ++ ui/src/pages/accesses/AccessNew.tsx | 4 +++- ui/vite.config.ts | 2 +- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 0b37526bb..925bace36 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -135,6 +135,8 @@ const AccessForm = forwardRef(({ className, return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.CA); case "notification": return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.NOTIFICATION); + default: + console.warn(`[certimate] unsupported provider usage: '${usage}'`); } return undefined; diff --git a/ui/src/components/workflow/WorkflowElement.tsx b/ui/src/components/workflow/WorkflowElement.tsx index 67df8974e..eefcb7779 100644 --- a/ui/src/components/workflow/WorkflowElement.tsx +++ b/ui/src/components/workflow/WorkflowElement.tsx @@ -60,7 +60,7 @@ const WorkflowElement = ({ node, disabled, branchId, branchIndex }: WorkflowElem return ; default: - console.warn(`[certimate] unsupported workflow node type: ${node.type}`); + console.warn(`[certimate] unsupported workflow node type: '${node.type}'`); return ; } }, [node, disabled, branchId, branchIndex]); diff --git a/ui/src/global.css b/ui/src/global.css index 1a7b2b175..0cf2aee9c 100644 --- a/ui/src/global.css +++ b/ui/src/global.css @@ -7,6 +7,8 @@ @import "antd/dist/reset.css"; @custom-variant dark (&:where(.dark, .dark *)); +@source inline("gap-{0,{1..96}}"); +@source inline("grid-cols-{0,{1..12}}"); @theme { --color-background: var(---twColorBackground); diff --git a/ui/src/pages/accesses/AccessNew.tsx b/ui/src/pages/accesses/AccessNew.tsx index fc036a63a..79440eed0 100644 --- a/ui/src/pages/accesses/AccessNew.tsx +++ b/ui/src/pages/accesses/AccessNew.tsx @@ -41,9 +41,11 @@ const AccessNew = () => { return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.CA); case "notification": return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.NOTIFICATION); + default: + console.warn(`[certimate] unsupported provider usage: '${providerUsage}'`); } - return undefined; + return () => false; }, [providerUsage]); const handleProviderPick = (value: string) => { diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 74a3cd296..2ea3a34e0 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -44,7 +44,7 @@ export default defineConfig(({ mode }) => { })?.toString(); } catch (error) { const err = error as SpawnSyncReturns; - console.warn("[Warn] failed to get version number through git", err?.stderr?.toString()); + console.warn("failed to get version number through git", err?.stderr?.toString()); } } From 9db0530ee447d486664bb6aadbdc4ac7f072abc6 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 23 Jul 2025 11:21:24 +0800 Subject: [PATCH 02/13] feat(ui): replace action buttons with dropdown menus in list pages --- ui/src/pages/accesses/AccessList.tsx | 83 ++++++++++-------- ui/src/pages/certificates/CertificateList.tsx | 57 +++++++------ ui/src/pages/dashboard/Dashboard.tsx | 53 +++++++----- ui/src/pages/workflows/WorkflowList.tsx | 84 ++++++++++--------- 4 files changed, 153 insertions(+), 124 deletions(-) diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index 3b098fb3b..3a3af7ba5 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { IconCirclePlus, IconCopy, IconEdit, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react"; +import { IconCirclePlus, IconCopy, IconDotsVertical, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react"; import { useRequest } from "ahooks"; -import { App, Avatar, Button, Input, Skeleton, Table, type TableProps, Tabs, Tooltip, Typography } from "antd"; +import { App, Avatar, Button, Dropdown, Input, Skeleton, Table, type TableProps, Tabs, Typography } from "antd"; import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; @@ -77,42 +77,51 @@ const AccessList = () => { key: "$action", align: "end", fixed: "right", - width: 120, + width: 64, render: (_, record) => ( -
- -
), }, diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index c44058928..798bc7d3c 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { IconBrowserShare, IconCertificate, IconExternalLink, IconReload, IconTrash } from "@tabler/icons-react"; +import { IconCertificate, IconDotsVertical, IconExternalLink, IconReload, IconTrash } from "@tabler/icons-react"; import { useRequest } from "ahooks"; -import { App, Button, Input, Segmented, Skeleton, Table, type TableProps, Tooltip, Typography } from "antd"; +import { App, Button, Dropdown, Input, Segmented, Skeleton, Table, type TableProps, Typography } from "antd"; import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; @@ -136,31 +136,36 @@ const CertificateList = () => { key: "$action", align: "end", fixed: "right", - width: 120, + width: 64, render: (_, record) => ( -
- -
), }, diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx index 85773609e..f08f9948d 100644 --- a/ui/src/pages/dashboard/Dashboard.tsx +++ b/ui/src/pages/dashboard/Dashboard.tsx @@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { IconActivity, - IconBrowserShare, IconHierarchy3, IconPlugConnected, IconPlus, @@ -255,16 +254,6 @@ const WorkflowRunHistoryTable = () => { return <>; }, }, - { - key: "$action", - align: "end", - width: 120, - render: (_, record) => ( -
- } variant="text" />} /> -
- ), - }, ]; const [tableData, setTableData] = useState([]); const { loading: tableLoading } = useRequest( @@ -292,19 +281,37 @@ const WorkflowRunHistoryTable = () => { } ); + const [detailRecord, setDetailRecord] = useState(); + const [detailOpen, setDetailOpen] = useState(false); + + const handleRecordDetailClick = (workflowRun: WorkflowRunModel) => { + setDetailRecord(workflowRun); + setDetailOpen(true); + }; + return ( - - columns={tableColumns} - dataSource={tableData} - loading={tableLoading} - locale={{ - emptyText: , - }} - pagination={false} - rowKey={(record) => record.id} - scroll={{ x: "max(100%, 720px)" }} - size="small" - /> + <> + + columns={tableColumns} + dataSource={tableData} + loading={tableLoading} + locale={{ + emptyText: , + }} + pagination={false} + rowClassName="cursor-pointer" + rowKey={(record) => record.id} + scroll={{ x: "max(100%, 720px)" }} + size="small" + onRow={(record) => ({ + onClick: () => { + handleRecordDetailClick(record); + }, + })} + /> + + + ); }; diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index a41c69533..60c7d4fc0 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { IconCirclePlus, IconCopy, IconEdit, IconHierarchy3, IconPlus, IconReload, IconTrash } from "@tabler/icons-react"; +import { IconCirclePlus, IconCopy, IconDotsVertical, IconHierarchy3, IconPlus, IconReload, IconTrash } from "@tabler/icons-react"; import { useRequest } from "ahooks"; -import { App, Button, Flex, Input, Segmented, Skeleton, Switch, Table, type TableProps, Tooltip, Typography } from "antd"; +import { App, Button, Dropdown, Flex, Input, Segmented, Skeleton, Switch, Table, type TableProps, Typography } from "antd"; import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; @@ -123,43 +123,51 @@ const WorkflowList = () => { key: "$action", align: "end", fixed: "right", - width: 120, + width: 64, render: (_, record) => ( -
- -
), }, From 833100632db29ce187e8ea0b2de47016e44c15fc Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 23 Jul 2025 20:14:28 +0800 Subject: [PATCH 03/13] feat(ui): support batch deletion --- internal/app/app.go | 17 ++ ui/src/components/access/AccessEditDrawer.tsx | 27 ++- ui/src/components/access/AccessForm.tsx | 14 +- ui/src/components/access/AccessSelect.tsx | 8 +- .../certificate/CertificateDetailDrawer.tsx | 28 ++- .../provider/ACMEDns01ProviderSelect.tsx | 10 +- .../provider/AccessProviderPicker.tsx | 10 +- .../provider/AccessProviderSelect.tsx | 10 +- .../components/provider/CAProviderSelect.tsx | 10 +- .../provider/DeploymentProviderPicker.tsx | 10 +- .../provider/DeploymentProviderSelect.tsx | 10 +- .../provider/NotificationProviderSelect.tsx | 10 +- .../workflow/WorkflowRunDetailDrawer.tsx | 28 ++- ui/src/components/workflow/WorkflowRuns.tsx | 15 +- .../workflow/node/ApplyNodeConfigForm.tsx | 34 +-- .../workflow/node/DeployNodeConfigForm.tsx | 14 +- .../workflow/node/NotifyNodeConfigForm.tsx | 20 +- ui/src/i18n/locales/en/nls.access.json | 8 +- ui/src/i18n/locales/en/nls.certificate.json | 6 +- ui/src/i18n/locales/en/nls.settings.json | 4 +- ui/src/i18n/locales/en/nls.workflow.json | 10 +- .../i18n/locales/en/nls.workflow.nodes.json | 6 +- ui/src/i18n/locales/en/nls.workflow.runs.json | 4 +- ui/src/i18n/locales/zh/nls.access.json | 6 +- ui/src/i18n/locales/zh/nls.certificate.json | 6 +- ui/src/i18n/locales/zh/nls.workflow.json | 10 +- ui/src/i18n/locales/zh/nls.workflow.runs.json | 4 +- ui/src/pages/accesses/AccessList.tsx | 178 +++++++++++----- ui/src/pages/accesses/AccessNew.tsx | 12 +- ui/src/pages/certificates/CertificateList.tsx | 200 ++++++++++++------ ui/src/pages/dashboard/Dashboard.tsx | 5 +- ui/src/pages/settings/SettingsAccount.tsx | 4 +- ui/src/pages/workflows/WorkflowDetail.tsx | 4 +- ui/src/pages/workflows/WorkflowList.tsx | 196 +++++++++++------ ui/src/repository/access.ts | 21 +- ui/src/repository/certificate.ts | 21 +- ui/src/repository/workflow.ts | 15 +- ui/src/stores/access/index.ts | 20 +- 38 files changed, 689 insertions(+), 326 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 62de2c9a3..04f42d189 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -23,6 +23,23 @@ func GetApp() core.App { pb.RootCmd.Flags().MarkHidden("encryptionEnv") pb.RootCmd.Flags().MarkHidden("queryTimeout") + pb.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error { + err := e.Next() + if err != nil { + return err + } + + settings := pb.Settings() + settings.Batch.Enabled = true + settings.Batch.MaxRequests = 1000 + settings.Batch.Timeout = 30 + if err := pb.Save(settings); err != nil { + return err + } + + return nil + }) + instance = pb }) diff --git a/ui/src/components/access/AccessEditDrawer.tsx b/ui/src/components/access/AccessEditDrawer.tsx index 13ccb2d2e..0a1d29f79 100644 --- a/ui/src/components/access/AccessEditDrawer.tsx +++ b/ui/src/components/access/AccessEditDrawer.tsx @@ -126,4 +126,29 @@ const AccessEditDrawer = ({ mode, data, loading, trigger, usage, afterSubmit, .. ); }; -export default AccessEditDrawer; +const useProps = () => { + const [data, setData] = useState(); + const [open, setOpen] = useState(false); + + const onOpenChange = (open: boolean) => { + setOpen(open); + + if (!open) { + setData(undefined); + } + }; + + return { + data, + open, + setData, + setOpen, + onOpenChange, + }; +}; + +const _default = Object.assign(AccessEditDrawer, { + useProps, +}); + +export default _default; diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 925bace36..2109505fc 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -126,15 +126,15 @@ const AccessForm = forwardRef(({ className, const providerFilter = useMemo(() => { switch (usage) { case "dns": - return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.DNS); + return (_: string, option: AccessProvider) => option.usages.includes(ACCESS_USAGES.DNS); case "hosting": - return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.HOSTING); + return (_: string, option: AccessProvider) => option.usages.includes(ACCESS_USAGES.HOSTING); case "dns-hosting": - return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.DNS) || record.usages.includes(ACCESS_USAGES.HOSTING); + return (_: string, option: AccessProvider) => option.usages.includes(ACCESS_USAGES.DNS) || option.usages.includes(ACCESS_USAGES.HOSTING); case "ca": - return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.CA); + return (_: string, option: AccessProvider) => option.usages.includes(ACCESS_USAGES.CA); case "notification": - return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.NOTIFICATION); + return (_: string, option: AccessProvider) => option.usages.includes(ACCESS_USAGES.NOTIFICATION); default: console.warn(`[certimate] unsupported provider usage: '${usage}'`); } @@ -371,9 +371,9 @@ const AccessForm = forwardRef(({ className, fallback={ } @@ -384,11 +384,11 @@ const AccessForm = forwardRef(({ className, diff --git a/ui/src/components/access/AccessSelect.tsx b/ui/src/components/access/AccessSelect.tsx index bb661558f..22be28098 100644 --- a/ui/src/components/access/AccessSelect.tsx +++ b/ui/src/components/access/AccessSelect.tsx @@ -8,10 +8,10 @@ import { useAccessesStore } from "@/stores/access"; export interface AccessTypeSelectProps extends Omit { - filter?: (record: AccessModel) => boolean; + onFilter?: (value: string, option: AccessModel) => boolean; } -const AccessSelect = ({ filter, ...props }: AccessTypeSelectProps) => { +const AccessSelect = ({ onFilter, ...props }: AccessTypeSelectProps) => { const { token: themeToken } = theme.useToken(); const { accesses, loadedAtOnce, fetchAccesses } = useAccessesStore(useZustandShallowSelector(["accesses", "loadedAtOnce", "fetchAccesses"])); @@ -21,7 +21,7 @@ const AccessSelect = ({ filter, ...props }: AccessTypeSelectProps) => { const [options, setOptions] = useState>([]); useEffect(() => { - const filteredItems = filter != null ? accesses.filter(filter) : accesses; + const filteredItems = onFilter != null ? accesses.filter((item) => onFilter(item.id, item)) : accesses; setOptions( filteredItems.map((item) => ({ key: item.id, @@ -30,7 +30,7 @@ const AccessSelect = ({ filter, ...props }: AccessTypeSelectProps) => { data: item, })) ); - }, [accesses, filter]); + }, [accesses, onFilter]); const renderOption = (key: string) => { const access = accesses.find((e) => e.id === key); diff --git a/ui/src/components/certificate/CertificateDetailDrawer.tsx b/ui/src/components/certificate/CertificateDetailDrawer.tsx index b5419b190..3c1740353 100644 --- a/ui/src/components/certificate/CertificateDetailDrawer.tsx +++ b/ui/src/components/certificate/CertificateDetailDrawer.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { useControllableValue } from "ahooks"; import { Drawer } from "antd"; @@ -45,4 +46,29 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica ); }; -export default CertificateDetailDrawer; +const useProps = () => { + const [data, setData] = useState(); + const [open, setOpen] = useState(false); + + const onOpenChange = (open: boolean) => { + setOpen(open); + + if (!open) { + setData(undefined); + } + }; + + return { + data, + open, + setData, + setOpen, + onOpenChange, + }; +}; + +const _default = Object.assign(CertificateDetailDrawer, { + useProps, +}); + +export default _default; diff --git a/ui/src/components/provider/ACMEDns01ProviderSelect.tsx b/ui/src/components/provider/ACMEDns01ProviderSelect.tsx index 8c6b919da..b52498d0f 100644 --- a/ui/src/components/provider/ACMEDns01ProviderSelect.tsx +++ b/ui/src/components/provider/ACMEDns01ProviderSelect.tsx @@ -6,10 +6,10 @@ import { type ACMEDns01Provider, acmeDns01ProvidersMap } from "@/domain/provider export interface ACMEDns01ProviderSelectProps extends Omit { - filter?: (record: ACMEDns01Provider) => boolean; + onFilter?: (value: string, option: ACMEDns01Provider) => boolean; } -const ACMEDns01ProviderSelect = ({ filter, ...props }: ACMEDns01ProviderSelectProps) => { +const ACMEDns01ProviderSelect = ({ onFilter, ...props }: ACMEDns01ProviderSelectProps) => { const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); @@ -17,8 +17,8 @@ const ACMEDns01ProviderSelect = ({ filter, ...props }: ACMEDns01ProviderSelectPr const options = useMemo>(() => { return Array.from(acmeDns01ProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -29,7 +29,7 @@ const ACMEDns01ProviderSelect = ({ filter, ...props }: ACMEDns01ProviderSelectPr label: t(provider.name), data: provider, })); - }, [filter]); + }, [onFilter]); const renderOption = (key: string) => { const provider = acmeDns01ProvidersMap.get(key); diff --git a/ui/src/components/provider/AccessProviderPicker.tsx b/ui/src/components/provider/AccessProviderPicker.tsx index 59c8f0d57..44373999f 100644 --- a/ui/src/components/provider/AccessProviderPicker.tsx +++ b/ui/src/components/provider/AccessProviderPicker.tsx @@ -11,14 +11,14 @@ export interface AccessProviderPickerProps { className?: string; style?: React.CSSProperties; autoFocus?: boolean; - filter?: (record: AccessProvider) => boolean; gap?: number | "small" | "middle" | "large"; placeholder?: string; showOptionTags?: boolean | { [key in AccessUsageType]?: boolean }; + onFilter?: (value: string, option: AccessProvider) => boolean; onSelect?: (value: string) => void; } -const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder, showOptionTags, onSelect, ...props }: AccessProviderPickerProps) => { +const AccessProviderPicker = ({ className, style, autoFocus, placeholder, showOptionTags, onFilter, onSelect, ...props }: AccessProviderPickerProps) => { const { gap = "middle" } = props; const { t } = useTranslation(); @@ -53,8 +53,8 @@ const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder const providers = useMemo(() => { return Array.from(accessProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -67,7 +67,7 @@ const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder return true; }); - }, [filter, keyword]); + }, [onFilter, keyword]); const providerCols = useMemo(() => { if (!wrapperSize) { return 1; diff --git a/ui/src/components/provider/AccessProviderSelect.tsx b/ui/src/components/provider/AccessProviderSelect.tsx index da81032a6..f595e7c4f 100644 --- a/ui/src/components/provider/AccessProviderSelect.tsx +++ b/ui/src/components/provider/AccessProviderSelect.tsx @@ -7,11 +7,11 @@ import { ACCESS_USAGES, type AccessProvider, type AccessUsageType, accessProvide export interface AccessProviderSelectProps extends Omit { - filter?: (record: AccessProvider) => boolean; showOptionTags?: boolean | { [key in AccessUsageType]?: boolean }; + onFilter?: (value: string, option: AccessProvider) => boolean; } -const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProviderSelectProps = { showOptionTags: true }) => { +const AccessProviderSelect = ({ showOptionTags, onFilter, ...props }: AccessProviderSelectProps = { showOptionTags: true }) => { const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); @@ -19,8 +19,8 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid const options = useMemo>(() => { return Array.from(accessProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -32,7 +32,7 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid disabled: provider.builtin, data: provider, })); - }, [filter]); + }, [onFilter]); const showOptionTagForDNS = useMemo(() => { return typeof showOptionTags === "object" ? !!showOptionTags?.[ACCESS_USAGES.DNS] : !!showOptionTags; diff --git a/ui/src/components/provider/CAProviderSelect.tsx b/ui/src/components/provider/CAProviderSelect.tsx index b1946e7ed..1d4616130 100644 --- a/ui/src/components/provider/CAProviderSelect.tsx +++ b/ui/src/components/provider/CAProviderSelect.tsx @@ -6,10 +6,10 @@ import { type CAProvider, caProvidersMap } from "@/domain/provider"; export interface CAProviderSelectProps extends Omit { - filter?: (record: CAProvider) => boolean; + onFilter?: (value: string, option: CAProvider) => boolean; } -const CAProviderSelect = ({ filter, ...props }: CAProviderSelectProps) => { +const CAProviderSelect = ({ onFilter, ...props }: CAProviderSelectProps) => { const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); @@ -17,8 +17,8 @@ const CAProviderSelect = ({ filter, ...props }: CAProviderSelectProps) => { const options = useMemo>(() => { const temp = Array.from(caProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -38,7 +38,7 @@ const CAProviderSelect = ({ filter, ...props }: CAProviderSelectProps) => { }); return temp; - }, [filter]); + }, [onFilter]); const renderOption = (key: string) => { if (key === "") { diff --git a/ui/src/components/provider/DeploymentProviderPicker.tsx b/ui/src/components/provider/DeploymentProviderPicker.tsx index 97b20ec46..175043d73 100644 --- a/ui/src/components/provider/DeploymentProviderPicker.tsx +++ b/ui/src/components/provider/DeploymentProviderPicker.tsx @@ -11,13 +11,13 @@ export interface DeploymentProviderPickerProps { className?: string; style?: React.CSSProperties; autoFocus?: boolean; - filter?: (record: DeploymentProvider) => boolean; gap?: number | "small" | "middle" | "large"; placeholder?: string; + onFilter?: (value: string, option: DeploymentProvider) => boolean; onSelect?: (value: string) => void; } -const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeholder, onSelect, ...props }: DeploymentProviderPickerProps) => { +const DeploymentProviderPicker = ({ className, style, autoFocus, onFilter, placeholder, onSelect, ...props }: DeploymentProviderPickerProps) => { const { gap = "middle" } = props; const { t } = useTranslation(); @@ -38,8 +38,8 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeho const providers = useMemo(() => { return Array.from(deploymentProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -59,7 +59,7 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeho return true; }); - }, [filter, category, keyword]); + }, [onFilter, category, keyword]); const providerCols = useMemo(() => { if (!wrapperSize) { return 1; diff --git a/ui/src/components/provider/DeploymentProviderSelect.tsx b/ui/src/components/provider/DeploymentProviderSelect.tsx index 8963be617..d213cf1a2 100644 --- a/ui/src/components/provider/DeploymentProviderSelect.tsx +++ b/ui/src/components/provider/DeploymentProviderSelect.tsx @@ -6,10 +6,10 @@ import { type DeploymentProvider, deploymentProvidersMap } from "@/domain/provid export interface DeploymentProviderSelectProps extends Omit { - filter?: (record: DeploymentProvider) => boolean; + onFilter?: (value: string, option: DeploymentProvider) => boolean; } -const DeploymentProviderSelect = ({ filter, ...props }: DeploymentProviderSelectProps) => { +const DeploymentProviderSelect = ({ onFilter, ...props }: DeploymentProviderSelectProps) => { const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); @@ -17,8 +17,8 @@ const DeploymentProviderSelect = ({ filter, ...props }: DeploymentProviderSelect const options = useMemo>(() => { return Array.from(deploymentProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -29,7 +29,7 @@ const DeploymentProviderSelect = ({ filter, ...props }: DeploymentProviderSelect label: t(provider.name), data: provider, })); - }, [filter]); + }, [onFilter]); const renderOption = (key: string) => { const provider = deploymentProvidersMap.get(key); diff --git a/ui/src/components/provider/NotificationProviderSelect.tsx b/ui/src/components/provider/NotificationProviderSelect.tsx index 5846cfea9..c595743ec 100644 --- a/ui/src/components/provider/NotificationProviderSelect.tsx +++ b/ui/src/components/provider/NotificationProviderSelect.tsx @@ -6,10 +6,10 @@ import { type NotificationProvider, notificationProvidersMap } from "@/domain/pr export interface NotificationProviderSelectProps extends Omit { - filter?: (record: NotificationProvider) => boolean; + onFilter?: (value: string, option: NotificationProvider) => boolean; } -const NotificationProviderSelect = ({ filter, ...props }: NotificationProviderSelectProps) => { +const NotificationProviderSelect = ({ onFilter, ...props }: NotificationProviderSelectProps) => { const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); @@ -17,8 +17,8 @@ const NotificationProviderSelect = ({ filter, ...props }: NotificationProviderSe const options = useMemo>(() => { return Array.from(notificationProvidersMap.values()) .filter((provider) => { - if (filter) { - return filter(provider); + if (onFilter) { + return onFilter(provider.type, provider); } return true; @@ -29,7 +29,7 @@ const NotificationProviderSelect = ({ filter, ...props }: NotificationProviderSe label: t(provider.name), data: provider, })); - }, [filter]); + }, [onFilter]); const renderOption = (key: string) => { const provider = notificationProvidersMap.get(key); diff --git a/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx b/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx index 82ccfabde..e8a6a36bc 100644 --- a/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx +++ b/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { useControllableValue } from "ahooks"; import { Drawer } from "antd"; @@ -46,4 +47,29 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR ); }; -export default WorkflowRunDetailDrawer; +const useProps = () => { + const [data, setData] = useState(); + const [open, setOpen] = useState(false); + + const onOpenChange = (open: boolean) => { + setOpen(open); + + if (!open) { + setData(undefined); + } + }; + + return { + data, + open, + setData, + setOpen, + onOpenChange, + }; +}; + +const _default = Object.assign(WorkflowRunDetailDrawer, { + useProps, +}); + +export default _default; diff --git a/ui/src/components/workflow/WorkflowRuns.tsx b/ui/src/components/workflow/WorkflowRuns.tsx index 82792ab1d..2feace06b 100644 --- a/ui/src/components/workflow/WorkflowRuns.tsx +++ b/ui/src/components/workflow/WorkflowRuns.tsx @@ -206,8 +206,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { }; }, [tableData]); - const [detailRecord, setDetailRecord] = useState(); - const [detailOpen, setDetailOpen] = useState(false); + const { setData: setDetailRecord, setOpen: setDetailOpen, ...detailDrawerProps } = WorkflowRunDetailDrawer.useProps(); const handleRecordDetailClick = (workflowRun: WorkflowRunModel) => { setDetailRecord(workflowRun); @@ -234,14 +233,8 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { const handleRecordDeleteClick = (workflowRun: WorkflowRunModel) => { modal.confirm({ - title: {t("workflow_run.action.modal.title")}, - content: ( - - ), + title: {t("workflow_run.action.delete.modal.title", { name: `#${workflowRun.id}` })}, + content: , icon: ( @@ -303,7 +296,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { })} /> - + ); }; diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index e1e495fe6..a4ce1c950 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -300,15 +300,15 @@ const ApplyNodeConfigForm = forwardRef