feat: search by keyword on AccessList, CertificateList, WorkflowList

This commit is contained in:
Fu Diwei 2025-02-13 20:31:11 +08:00
parent 970fba90e0
commit 664bb692b6
15 changed files with 262 additions and 144 deletions

View File

@ -212,7 +212,6 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
const handleProviderSelect = (value: string) => { const handleProviderSelect = (value: string) => {
if (fieldProvider === value) return; if (fieldProvider === value) return;
// TODO: 暂时不支持切换部署目标,需后端调整,否则之前若存在部署结果输出就不会再部署
// 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标 // 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标
if (initialValues?.provider === value) { if (initialValues?.provider === value) {
formInst.resetFields(); formInst.resetFields();
@ -276,13 +275,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
fallback={<DeployProviderPicker autoFocus placeholder={t("workflow_node.deploy.search.provider.placeholder")} onSelect={handleProviderPick} />} fallback={<DeployProviderPicker autoFocus placeholder={t("workflow_node.deploy.search.provider.placeholder")} onSelect={handleProviderPick} />}
> >
<Form.Item name="provider" label={t("workflow_node.deploy.form.provider.label")} rules={[formRule]}> <Form.Item name="provider" label={t("workflow_node.deploy.form.provider.label")} rules={[formRule]}>
<DeployProviderSelect <DeployProviderSelect allowClear placeholder={t("workflow_node.deploy.form.provider.placeholder")} showSearch onSelect={handleProviderSelect} />
allowClear
disabled={!!initialValues?.provider}
placeholder={t("workflow_node.deploy.form.provider.placeholder")}
showSearch
onSelect={handleProviderSelect}
/>
</Form.Item> </Form.Item>
<Form.Item className="mb-0"> <Form.Item className="mb-0">

View File

@ -3,6 +3,8 @@
"access.nodata": "No accesses. Please create an authorization first.", "access.nodata": "No accesses. Please create an authorization first.",
"access.search.placeholder": "Search by access name ...",
"access.action.add": "Create authorization", "access.action.add": "Create authorization",
"access.action.edit": "Edit authorization", "access.action.edit": "Edit authorization",
"access.action.duplicate": "Duplicate authorization", "access.action.duplicate": "Duplicate authorization",

View File

@ -3,6 +3,8 @@
"certificate.nodata": "No certificates. Please create a workflow to generate certificates! 😀", "certificate.nodata": "No certificates. Please create a workflow to generate certificates! 😀",
"certificate.search.placeholder": "Search by certificate name or serial number ...",
"certificate.action.view": "View certificate", "certificate.action.view": "View certificate",
"certificate.action.delete": "Delete certificate", "certificate.action.delete": "Delete certificate",
"certificate.action.delete.confirm": "Are you sure to delete this certificate?", "certificate.action.delete.confirm": "Are you sure to delete this certificate?",

View File

@ -3,6 +3,8 @@
"workflow.nodata": "No workflows. Please create a workflow to generate certificates! 😀", "workflow.nodata": "No workflows. Please create a workflow to generate certificates! 😀",
"workflow.search.placeholder": "Search by workflow name ...",
"workflow.action.create": "Create workflow", "workflow.action.create": "Create workflow",
"workflow.action.edit": "Edit workflow", "workflow.action.edit": "Edit workflow",
"workflow.action.delete": "Delete workflow", "workflow.action.delete": "Delete workflow",

View File

@ -3,6 +3,8 @@
"access.nodata": "暂无授权信息,请先新建授权", "access.nodata": "暂无授权信息,请先新建授权",
"access.search.placeholder": "按授权名称搜索……",
"access.action.add": "新建授权", "access.action.add": "新建授权",
"access.action.edit": "编辑授权", "access.action.edit": "编辑授权",
"access.action.duplicate": "复制授权", "access.action.duplicate": "复制授权",

View File

@ -3,6 +3,8 @@
"certificate.nodata": "暂无证书,新建一个工作流去生成证书吧~ 😀", "certificate.nodata": "暂无证书,新建一个工作流去生成证书吧~ 😀",
"certificate.search.placeholder": "按证书名称或序列号搜索……",
"certificate.action.view": "查看证书", "certificate.action.view": "查看证书",
"certificate.action.delete": "删除证书", "certificate.action.delete": "删除证书",
"certificate.action.delete.confirm": "确定要删除此证书吗?", "certificate.action.delete.confirm": "确定要删除此证书吗?",

View File

@ -3,6 +3,8 @@
"workflow.nodata": "暂无工作流,请先新建工作流", "workflow.nodata": "暂无工作流,请先新建工作流",
"workflow.search.placeholder": "按工作流名称搜索……",
"workflow.action.create": "新建工作流", "workflow.action.create": "新建工作流",
"workflow.action.edit": "编辑工作流", "workflow.action.edit": "编辑工作流",
"workflow.action.delete": "删除工作流", "workflow.action.delete": "删除工作流",

View File

@ -1,14 +1,16 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { import {
DeleteOutlined as DeleteOutlinedIcon, DeleteOutlined as DeleteOutlinedIcon,
EditOutlined as EditOutlinedIcon, EditOutlined as EditOutlinedIcon,
PlusOutlined as PlusOutlinedIcon, PlusOutlined as PlusOutlinedIcon,
ReloadOutlined as ReloadOutlinedIcon,
SnippetsOutlined as SnippetsOutlinedIcon, SnippetsOutlined as SnippetsOutlinedIcon,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { Avatar, Button, Empty, Modal, Space, Table, type TableProps, Tooltip, Typography, notification } from "antd"; import { Avatar, Button, Card, Empty, Flex, Input, Modal, Space, Table, type TableProps, Tooltip, Typography, notification } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -20,6 +22,8 @@ import { useAccessesStore } from "@/stores/access";
import { getErrMsg } from "@/utils/error"; import { getErrMsg } from "@/utils/error";
const AccessList = () => { const AccessList = () => {
const [searchParams] = useSearchParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [modalApi, ModelContextHolder] = Modal.useModal(); const [modalApi, ModelContextHolder] = Modal.useModal();
@ -116,6 +120,12 @@ const AccessList = () => {
const [tableData, setTableData] = useState<AccessModel[]>([]); const [tableData, setTableData] = useState<AccessModel[]>([]);
const [tableTotal, setTableTotal] = useState<number>(0); const [tableTotal, setTableTotal] = useState<number>(0);
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return {
keyword: searchParams.get("keyword"),
};
});
const [page, setPage] = useState<number>(1); const [page, setPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10); const [pageSize, setPageSize] = useState<number>(10);
@ -134,14 +144,21 @@ const AccessList = () => {
() => { () => {
const startIndex = (page - 1) * pageSize; const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize; const endIndex = startIndex + pageSize;
const items = accesses.slice(startIndex, endIndex); const list = accesses.filter((e) => {
const keyword = (filters["keyword"] as string | undefined)?.trim();
if (keyword) {
return e.name.includes(keyword);
}
return true;
});
return Promise.resolve({ return Promise.resolve({
items, items: list.slice(startIndex, endIndex),
totalItems: accesses.length, totalItems: list.length,
}); });
}, },
{ {
refreshDeps: [accesses, page, pageSize], refreshDeps: [accesses, filters, page, pageSize],
onSuccess: (res) => { onSuccess: (res) => {
setTableData(res.items); setTableData(res.items);
setTableTotal(res.totalItems); setTableTotal(res.totalItems);
@ -149,6 +166,16 @@ const AccessList = () => {
} }
); );
const handleSearch = (value: string) => {
setFilters((prev) => ({ ...prev, keyword: value }));
};
const handleReloadClick = () => {
if (loading) return;
fetchAccesses();
};
const handleDeleteClick = async (data: AccessModel) => { const handleDeleteClick = async (data: AccessModel) => {
modalApi.confirm({ modalApi.confirm({
title: t("access.action.delete"), title: t("access.action.delete"),
@ -186,30 +213,43 @@ const AccessList = () => {
]} ]}
/> />
<Table<AccessModel> <Card size="small">
columns={tableColumns} <div className="mb-4">
dataSource={tableData} <Flex gap="small">
loading={!loadedAtOnce || loading} <div className="flex-1">
locale={{ <Input.Search allowClear defaultValue={filters["keyword"] as string} placeholder={t("access.search.placeholder")} onSearch={handleSearch} />
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("access.nodata")} />, </div>
}} <div>
pagination={{ <Button icon={<ReloadOutlinedIcon spin={loading} />} onClick={handleReloadClick} />
current: page, </div>
pageSize: pageSize, </Flex>
total: tableTotal, </div>
showSizeChanger: true,
onChange: (page: number, pageSize: number) => { <Table<AccessModel>
setPage(page); columns={tableColumns}
setPageSize(pageSize); dataSource={tableData}
}, loading={!loadedAtOnce || loading}
onShowSizeChange: (page: number, pageSize: number) => { locale={{
setPage(page); emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("access.nodata")} />,
setPageSize(pageSize); }}
}, pagination={{
}} current: page,
rowKey={(record) => record.id} pageSize: pageSize,
scroll={{ x: "max(100%, 960px)" }} total: tableTotal,
/> showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
</Card>
</div> </div>
); );
}; };

View File

@ -1,10 +1,28 @@
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { DeleteOutlined as DeleteOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons"; import { DeleteOutlined as DeleteOutlinedIcon, ReloadOutlined as ReloadOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { Button, Divider, Empty, Menu, type MenuProps, Modal, Radio, Space, Table, type TableProps, Tooltip, Typography, notification, theme } from "antd"; import {
Button,
Card,
Divider,
Empty,
Flex,
Input,
Menu,
type MenuProps,
Modal,
Radio,
Space,
Table,
type TableProps,
Tooltip,
Typography,
notification,
theme,
} from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -191,6 +209,7 @@ const CertificateList = () => {
const [filters, setFilters] = useState<Record<string, unknown>>(() => { const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return { return {
keyword: searchParams.get("keyword"),
state: searchParams.get("state"), state: searchParams.get("state"),
}; };
}); });
@ -205,9 +224,10 @@ const CertificateList = () => {
} = useRequest( } = useRequest(
() => { () => {
return listCertificate({ return listCertificate({
keyword: filters["keyword"] as string,
state: filters["state"] as ListCertificateRequest["state"],
page: page, page: page,
perPage: pageSize, perPage: pageSize,
state: filters["state"] as ListCertificateRequest["state"],
}); });
}, },
{ {
@ -229,6 +249,16 @@ const CertificateList = () => {
} }
); );
const handleSearch = (value: string) => {
setFilters((prev) => ({ ...prev, keyword: value.trim() }));
};
const handleReloadClick = () => {
if (loading) return;
refreshData();
};
const handleDeleteClick = (certificate: CertificateModel) => { const handleDeleteClick = (certificate: CertificateModel) => {
modalApi.confirm({ modalApi.confirm({
title: t("certificate.action.delete"), title: t("certificate.action.delete"),
@ -255,30 +285,43 @@ const CertificateList = () => {
<PageHeader title={t("certificate.page.title")} /> <PageHeader title={t("certificate.page.title")} />
<Table<CertificateModel> <Card size="small">
columns={tableColumns} <div className="mb-4">
dataSource={tableData} <Flex gap="small">
loading={loading} <div className="flex-1">
locale={{ <Input.Search allowClear defaultValue={filters["keyword"] as string} placeholder={t("certificate.search.placeholder")} onSearch={handleSearch} />
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("certificate.nodata"))} />, </div>
}} <div>
pagination={{ <Button icon={<ReloadOutlinedIcon spin={loading} />} onClick={handleReloadClick} />
current: page, </div>
pageSize: pageSize, </Flex>
total: tableTotal, </div>
showSizeChanger: true,
onChange: (page: number, pageSize: number) => { <Table<CertificateModel>
setPage(page); columns={tableColumns}
setPageSize(pageSize); dataSource={tableData}
}, loading={loading}
onShowSizeChange: (page: number, pageSize: number) => { locale={{
setPage(page); emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("certificate.nodata"))} />,
setPageSize(pageSize); }}
}, pagination={{
}} current: page,
rowKey={(record) => record.id} pageSize: pageSize,
scroll={{ x: "max(100%, 960px)" }} total: tableTotal,
/> showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
</Card>
</div> </div>
); );
}; };

View File

@ -8,6 +8,7 @@ import {
DeleteOutlined as DeleteOutlinedIcon, DeleteOutlined as DeleteOutlinedIcon,
EditOutlined as EditOutlinedIcon, EditOutlined as EditOutlinedIcon,
PlusOutlined as PlusOutlinedIcon, PlusOutlined as PlusOutlinedIcon,
ReloadOutlined as ReloadOutlinedIcon,
StopOutlined as StopOutlinedIcon, StopOutlined as StopOutlinedIcon,
SyncOutlined as SyncOutlinedIcon, SyncOutlined as SyncOutlinedIcon,
} from "@ant-design/icons"; } from "@ant-design/icons";
@ -16,8 +17,11 @@ import { PageHeader } from "@ant-design/pro-components";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { import {
Button, Button,
Card,
Divider, Divider,
Empty, Empty,
Flex,
Input,
Menu, Menu,
type MenuProps, type MenuProps,
Modal, Modal,
@ -235,6 +239,7 @@ const WorkflowList = () => {
const [filters, setFilters] = useState<Record<string, unknown>>(() => { const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return { return {
keyword: searchParams.get("keyword"),
state: searchParams.get("state"), state: searchParams.get("state"),
}; };
}); });
@ -249,9 +254,10 @@ const WorkflowList = () => {
} = useRequest( } = useRequest(
() => { () => {
return listWorkflow({ return listWorkflow({
keyword: filters["keyword"] as string,
enabled: (filters["state"] as string) === "enabled" ? true : (filters["state"] as string) === "disabled" ? false : undefined,
page: page, page: page,
perPage: pageSize, perPage: pageSize,
enabled: (filters["state"] as string) === "enabled" ? true : (filters["state"] as string) === "disabled" ? false : undefined,
}); });
}, },
{ {
@ -273,10 +279,20 @@ const WorkflowList = () => {
} }
); );
const handleSearch = (value: string) => {
setFilters((prev) => ({ ...prev, keyword: value.trim() }));
};
const handleCreateClick = () => { const handleCreateClick = () => {
navigate("/workflows/new"); navigate("/workflows/new");
}; };
const handleReloadClick = () => {
if (loading) return;
refreshData();
};
const handleEnabledChange = async (workflow: WorkflowModel) => { const handleEnabledChange = async (workflow: WorkflowModel) => {
try { try {
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) { if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
@ -345,30 +361,43 @@ const WorkflowList = () => {
]} ]}
/> />
<Table<WorkflowModel> <Card size="small">
columns={tableColumns} <div className="mb-4">
dataSource={tableData} <Flex gap="small">
loading={loading} <div className="flex-1">
locale={{ <Input.Search allowClear defaultValue={filters["keyword"] as string} placeholder={t("workflow.search.placeholder")} onSearch={handleSearch} />
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("workflow.nodata"))} />, </div>
}} <div>
pagination={{ <Button icon={<ReloadOutlinedIcon spin={loading} />} onClick={handleReloadClick} />
current: page, </div>
pageSize: pageSize, </Flex>
total: tableTotal, </div>
showSizeChanger: true,
onChange: (page: number, pageSize: number) => { <Table<WorkflowModel>
setPage(page); columns={tableColumns}
setPageSize(pageSize); dataSource={tableData}
}, loading={loading}
onShowSizeChange: (page: number, pageSize: number) => { locale={{
setPage(page); emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("workflow.nodata"))} />,
setPageSize(pageSize); }}
}, pagination={{
}} current: page,
rowKey={(record) => record.id} pageSize: pageSize,
scroll={{ x: "max(100%, 960px)" }} total: tableTotal,
/> showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
</Card>
</div> </div>
); );
}; };

View File

@ -4,11 +4,16 @@ import { type AccessModel } from "@/domain/access";
import { COLLECTION_NAME_ACCESS, getPocketBase } from "./_pocketbase"; import { COLLECTION_NAME_ACCESS, getPocketBase } from "./_pocketbase";
export const list = async () => { export const list = async () => {
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).getFullList<AccessModel>({ const list = await getPocketBase().collection(COLLECTION_NAME_ACCESS).getFullList<AccessModel>({
batch: 65535,
filter: "deleted=null", filter: "deleted=null",
sort: "-created", sort: "-created",
requestKey: null, requestKey: null,
}); });
return {
totalItems: list.length,
items: list,
};
}; };
export const save = async (record: MaybeModelRecord<AccessModel>) => { export const save = async (record: MaybeModelRecord<AccessModel>) => {

View File

@ -1,55 +1,51 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { type RecordListOptions } from "pocketbase";
import { type CertificateModel } from "@/domain/certificate"; import { type CertificateModel } from "@/domain/certificate";
import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase"; import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase";
export type ListCertificateRequest = { export type ListCertificateRequest = {
keyword?: string;
state?: "expireSoon" | "expired";
page?: number; page?: number;
perPage?: number; perPage?: number;
state?: "expireSoon" | "expired";
}; };
export const list = async (request: ListCertificateRequest) => { export const list = async (request: ListCertificateRequest) => {
const pb = getPocketBase(); const pb = getPocketBase();
const page = request.page || 1; const filters: string[] = ["deleted=null"];
const perPage = request.perPage || 10; if (request.keyword) {
filters.push(pb.filter("(subjectAltNames~{:keyword} || serialNumber={:keyword})", { keyword: request.keyword }));
const options: RecordListOptions = { }
expand: "workflowId",
filter: "deleted=null",
sort: "-created",
requestKey: null,
};
if (request.state === "expireSoon") { if (request.state === "expireSoon") {
options.filter = pb.filter("expireAt<{:expiredAt} && deleted=null", { filters.push(pb.filter("expireAt<{:expiredAt}", { expiredAt: dayjs().add(20, "d").toDate() }));
expiredAt: dayjs().add(20, "d").toDate(),
});
} else if (request.state === "expired") { } else if (request.state === "expired") {
options.filter = pb.filter("expireAt<={:expiredAt} && deleted=null", { filters.push(pb.filter("expireAt<={:expiredAt}", { expiredAt: new Date() }));
expiredAt: new Date(),
});
} }
return pb.collection(COLLECTION_NAME_CERTIFICATE).getList<CertificateModel>(page, perPage, options); const page = request.page || 1;
const perPage = request.perPage || 10;
return pb.collection(COLLECTION_NAME_CERTIFICATE).getList<CertificateModel>(page, perPage, {
expand: "workflowId",
filter: filters.join(" && "),
sort: "-created",
requestKey: null,
});
}; };
export const listByWorkflowRunId = async (workflowRunId: string) => { export const listByWorkflowRunId = async (workflowRunId: string) => {
const pb = getPocketBase(); const pb = getPocketBase();
const options: RecordListOptions = { const list = await pb.collection(COLLECTION_NAME_CERTIFICATE).getFullList<CertificateModel>({
filter: pb.filter("workflowRunId={:workflowRunId}", { batch: 65535,
workflowRunId: workflowRunId, filter: pb.filter("workflowRunId={:workflowRunId}", { workflowRunId: workflowRunId }),
}),
sort: "-created", sort: "-created",
requestKey: null, requestKey: null,
}; });
const items = await pb.collection(COLLECTION_NAME_CERTIFICATE).getFullList<CertificateModel>(options);
return { return {
totalItems: items.length, totalItems: list.length,
items: items, items: list,
}; };
}; };

View File

@ -1,30 +1,33 @@
import { type RecordListOptions, type RecordSubscription } from "pocketbase"; import { type RecordSubscription } from "pocketbase";
import { type WorkflowModel } from "@/domain/workflow"; import { type WorkflowModel } from "@/domain/workflow";
import { COLLECTION_NAME_WORKFLOW, getPocketBase } from "./_pocketbase"; import { COLLECTION_NAME_WORKFLOW, getPocketBase } from "./_pocketbase";
export type ListWorkflowRequest = { export type ListWorkflowRequest = {
keyword?: string;
enabled?: boolean;
page?: number; page?: number;
perPage?: number; perPage?: number;
enabled?: boolean;
}; };
export const list = async (request: ListWorkflowRequest) => { export const list = async (request: ListWorkflowRequest) => {
const pb = getPocketBase(); const pb = getPocketBase();
const page = request.page || 1; const filters: string[] = [];
const perPage = request.perPage || 10; if (request.keyword) {
filters.push(pb.filter("name~{:keyword}", { keyword: request.keyword }));
const options: RecordListOptions = { }
sort: "-created",
requestKey: null,
};
if (request.enabled != null) { if (request.enabled != null) {
options.filter = pb.filter("enabled={:enabled}", { enabled: request.enabled }); filters.push(pb.filter("enabled={:enabled}", { enabled: request.enabled }));
} }
return await pb.collection(COLLECTION_NAME_WORKFLOW).getList<WorkflowModel>(page, perPage, options); const page = request.page || 1;
const perPage = request.perPage || 10;
return await pb.collection(COLLECTION_NAME_WORKFLOW).getList<WorkflowModel>(page, perPage, {
filter: filters.join(" && "),
sort: "-created",
requestKey: null,
});
}; };
export const get = async (id: string) => { export const get = async (id: string) => {

View File

@ -12,24 +12,21 @@ export type ListWorkflowRunsRequest = {
}; };
export const list = async (request: ListWorkflowRunsRequest) => { export const list = async (request: ListWorkflowRunsRequest) => {
const page = request.page || 1; const pb = getPocketBase();
const perPage = request.perPage || 10;
let filter = ""; const filters: string[] = [];
const params: Record<string, string> = {};
if (request.workflowId) { if (request.workflowId) {
filter = `workflowId={:workflowId}`; filters.push(pb.filter("workflowId={:workflowId}", { workflowId: request.workflowId }));
params.workflowId = request.workflowId;
} }
return await getPocketBase() const page = request.page || 1;
.collection(COLLECTION_NAME_WORKFLOW_RUN) const perPage = request.perPage || 10;
.getList<WorkflowRunModel>(page, perPage, { return await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).getList<WorkflowRunModel>(page, perPage, {
filter: getPocketBase().filter(filter, params), filter: filters.join(" && "),
sort: "-created", sort: "-created",
requestKey: null, requestKey: null,
expand: request.expand ? "workflowId" : undefined, expand: request.expand ? "workflowId" : undefined,
}); });
}; };
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => { export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => {

View File

@ -24,7 +24,7 @@ export const useAccessesStore = create<AccessesState>((set) => {
loadedAtOnce: false, loadedAtOnce: false,
fetchAccesses: async () => { fetchAccesses: async () => {
fetcher ??= listAccess(); fetcher ??= listAccess().then((res) => res.items);
try { try {
set({ loading: true }); set({ loading: true });