diff --git a/web/src/components/FileTypeIcon/index.tsx b/web/src/components/FileTypeIcon/index.tsx index 6fee17bf5b..6339612a41 100644 --- a/web/src/components/FileTypeIcon/index.tsx +++ b/web/src/components/FileTypeIcon/index.tsx @@ -27,6 +27,7 @@ export const FileType = { npm: "npm", db: "db", bucket: "bucket", + policy: "policy", }; export default function FileTypeIcon(props: { type: string }) { @@ -64,6 +65,13 @@ export default function FileTypeIcon(props: { type: string }) { ); + case FileType.policy: + return ( + + + + ); + case FileType.bucket: return ( diff --git a/web/src/components/Panel/index.tsx b/web/src/components/Panel/index.tsx index 4cac577758..8587ec24eb 100644 --- a/web/src/components/Panel/index.tsx +++ b/web/src/components/Panel/index.tsx @@ -7,11 +7,13 @@ const Panel = (props: { className?: string; style?: React.CSSProperties; children: React.ReactNode; + onClick?: () => void; }) => { - const { className, style = {} } = props; + const { className, style = {}, onClick } = props; return (
{props.children} diff --git a/web/src/components/SectionList/index.module.scss b/web/src/components/SectionList/index.module.scss index 3e6bf71c5c..4999747d44 100644 --- a/web/src/components/SectionList/index.module.scss +++ b/web/src/components/SectionList/index.module.scss @@ -18,6 +18,7 @@ &:hover, &.active { background-color: #eff9fa; + color: #008C7F; } &.active { diff --git a/web/src/pages/app/database/CollectionDataList/index.tsx b/web/src/pages/app/database/CollectionDataList/index.tsx index 1ba0acd66e..faadfd0129 100644 --- a/web/src/pages/app/database/CollectionDataList/index.tsx +++ b/web/src/pages/app/database/CollectionDataList/index.tsx @@ -5,9 +5,7 @@ import { Button } from "@chakra-ui/react"; import CreateCollectionModal from "../mods/CreateCollectionModal"; import useDBMStore from "../store"; -// import ColPannel from "./mods/ColPannel"; import DataPannel from "./mods/DataPannel"; -// import IndexPannel from "./mods/IndexPannel"; export default function CollectionDataList() { const { t } = useTranslation(); @@ -15,9 +13,9 @@ export default function CollectionDataList() { return ( <> {store.currentDB === undefined ? ( -
+
- diff --git a/web/src/pages/app/database/CollectionDataList/mods/DataPannel/index.tsx b/web/src/pages/app/database/CollectionDataList/mods/DataPannel/index.tsx index e1413f0c3d..f4f2555430 100644 --- a/web/src/pages/app/database/CollectionDataList/mods/DataPannel/index.tsx +++ b/web/src/pages/app/database/CollectionDataList/mods/DataPannel/index.tsx @@ -1,20 +1,24 @@ import { useMemo, useState } from "react"; import SyntaxHighlighter from "react-syntax-highlighter"; -import { AddIcon, EditIcon, Search2Icon } from "@chakra-ui/icons"; -import { Button, HStack, Input, InputGroup, InputLeftElement, Text } from "@chakra-ui/react"; -import clsx from "clsx"; +import { AddIcon, Search2Icon } from "@chakra-ui/icons"; +import { Button, HStack, Input, InputGroup, InputLeftElement } from "@chakra-ui/react"; import { debounce } from "lodash"; import JsonEditor from "@/components/Editor/JsonEditor"; -import IconWrap from "@/components/IconWrap"; import Pagination from "@/components/Pagination"; import Panel from "@/components/Panel"; import getPageInfo from "@/utils/getPageInfo"; -import { useAddDataMutation, useEntryDataQuery, useUpdateDataMutation } from "../../../service"; +import RightPanelEditBox from "../../../RightComponent/EditBox"; +import RightPanelList from "../../../RightComponent/List"; +import { + useAddDataMutation, + useDeleteDataMutation, + useEntryDataQuery, + useUpdateDataMutation, +} from "../../../service"; import useDBMStore from "../../../store"; -import DeleteButton from "./DeleteButton"; export default function DataPannel() { const [currentData, setCurrentData] = useState(undefined); @@ -46,8 +50,12 @@ export default function DataPannel() { setCurrentData({}); }); const addDataMutation = useAddDataMutation(); - const updateDataMutation = useUpdateDataMutation(); + const deleteDataMutation = useDeleteDataMutation({ + onSuccess() { + setCurrentData(undefined); + }, + }); const handleData = async () => { if (currentData?._id) { @@ -60,8 +68,8 @@ export default function DataPannel() { }; return ( - - + <> +
- {/* */} -
-
-
+
- + ); } diff --git a/web/src/pages/app/database/CollectionListPanel/MoreButton/index.tsx b/web/src/pages/app/database/CollectionListPanel/MoreButton/index.tsx index afb7e05e61..e3a98ad6cc 100644 --- a/web/src/pages/app/database/CollectionListPanel/MoreButton/index.tsx +++ b/web/src/pages/app/database/CollectionListPanel/MoreButton/index.tsx @@ -18,7 +18,7 @@ export default function MoreButton(props: { data?: any; fn?: any }) { closeOnBlur={true} placement="bottom" > - + - -
- - - - + +
+
+ + + + 复制 +
+
+ + 删除 +
diff --git a/web/src/pages/app/database/CollectionListPanel/index.tsx b/web/src/pages/app/database/CollectionListPanel/index.tsx index a5f3bf3139..bd6c752027 100644 --- a/web/src/pages/app/database/CollectionListPanel/index.tsx +++ b/web/src/pages/app/database/CollectionListPanel/index.tsx @@ -33,7 +33,12 @@ export default function CollectionListPanel() { const [search, setSearch] = useState(""); return ( - + { + store.setCurrentShow("DB"); + }} + >
- + {(collectionListQuery?.data?.data || []) .filter((db: any) => db.name.indexOf(search) >= 0) .map((db: any) => { return ( { store.setCurrentDB(db); @@ -80,7 +85,7 @@ export default function CollectionListPanel() {
diff --git a/web/src/pages/app/database/PolicyDataList/index.tsx b/web/src/pages/app/database/PolicyDataList/index.tsx new file mode 100644 index 0000000000..94ff04b6bc --- /dev/null +++ b/web/src/pages/app/database/PolicyDataList/index.tsx @@ -0,0 +1,133 @@ +import { useState } from "react"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { AddIcon } from "@chakra-ui/icons"; +import { Button, Select, Text } from "@chakra-ui/react"; + +import JsonEditor from "@/components/Editor/JsonEditor"; +import Panel from "@/components/Panel"; + +import RightPanelEditBox from "../RightComponent/EditBox"; +import RightPanelList from "../RightComponent/List"; +import { + useCollectionListQuery, + useCreateRulesMutation, + useDeleteRuleMutation, + useRulesListQuery, + useUpdateRulesMutation, +} from "../service"; +import useDBMStore from "../store"; + +export default function PolicyDataList() { + const collectionListQuery = useCollectionListQuery(); + const [currentData, setCurrentData] = useState(undefined); + const [record, setRecord] = useState(""); + const [collectionName, setCollectionName] = useState(collectionListQuery?.data?.data[0]?.name); + + const store = useDBMStore((state) => state); + + const rulesListQuery = useRulesListQuery((data: any) => { + if (data?.data.length === 0) { + setCurrentData({}); + setCollectionName(collectionListQuery?.data?.data[0]?.name); + } + }); + const deleteRuleMutation = useDeleteRuleMutation(() => { + rulesListQuery.refetch(); + }); + const createRulesMutation = useCreateRulesMutation(); + const updateRulesMutation = useUpdateRulesMutation(); + + const handleData = async () => { + if (currentData?.id) { + await updateRulesMutation.mutateAsync({ collection: collectionName, value: record }); + } else { + await createRulesMutation.mutateAsync({ collectionName: collectionName, value: record }); + } + rulesListQuery.refetch(); + }; + + return ( + <> + + + 规则数:{rulesListQuery?.data?.data?.length || 0} + +
+ currentData?.id === item.id} + onClick={(data: any) => { + setCurrentData(data); + }} + deleteRuleMutation={deleteRuleMutation} + deleteData={(item) => ({ collection: item.collectionName })} + component={(item: any) => { + return ( + <> +
+ + 集合:{item.collectionName} + +
+ + {JSON.stringify(item.value, null, 2)} + + + ); + }} + /> + + + 选择集合 + + + + 规则内容 + +
+ { + setRecord(values!); + }} + /> +
+
+
+ + ); +} diff --git a/web/src/pages/app/database/PolicyListPanel/index.tsx b/web/src/pages/app/database/PolicyListPanel/index.tsx new file mode 100644 index 0000000000..e82321c6fd --- /dev/null +++ b/web/src/pages/app/database/PolicyListPanel/index.tsx @@ -0,0 +1,88 @@ +/**************************** + * cloud functions list sidebar + ***************************/ +import { AddIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; +import { t } from "i18next"; + +import ConfirmButton from "@/components/ConfirmButton"; +import FileTypeIcon from "@/components/FileTypeIcon"; +import IconWrap from "@/components/IconWrap"; +import Panel from "@/components/Panel"; +import SectionList from "@/components/SectionList"; + +import AddPolicyModal from "../mods/AddPolicyModal"; +import { useDeletePolicyMutation, usePolicyListQuery } from "../service"; +import useDBMStore from "../store"; +export default function PolicyListPanel() { + const policyQuery = usePolicyListQuery((data) => { + if (data.data.length > 0) { + store.setCurrentPolicy(data.data[0]); + } else { + store.setCurrentPolicy(undefined); + } + }); + + const deletePolicyMutation = useDeletePolicyMutation(); + const store = useDBMStore((state) => state); + return ( + { + store.setCurrentShow("Policy"); + }} + > + + + + + , + ]} + /> + + {policyQuery?.data?.data.map((item: any) => { + return ( + { + store.setCurrentPolicy(item); + }} + > +
+
+ + {item.name} +
+
+ + + + + + { + await deletePolicyMutation.mutateAsync(item.name); + }} + headerText={String(t("Delete"))} + bodyText="确定删除该策略?" + > + + + + +
+
+
+ ); + })} +
+
+ ); +} diff --git a/web/src/pages/app/database/CollectionDataList/mods/DataPannel/DeleteButton.tsx b/web/src/pages/app/database/RightComponent/DeleteButton.tsx similarity index 79% rename from web/src/pages/app/database/CollectionDataList/mods/DataPannel/DeleteButton.tsx rename to web/src/pages/app/database/RightComponent/DeleteButton.tsx index 659d3559e0..8dc9052b8c 100644 --- a/web/src/pages/app/database/CollectionDataList/mods/DataPannel/DeleteButton.tsx +++ b/web/src/pages/app/database/RightComponent/DeleteButton.tsx @@ -10,16 +10,8 @@ import { import IconWrap from "@/components/IconWrap"; -import { useDeleteDataMutation } from "../../../service"; - -export default function DeleteButton(props: { data: any; fn: any }) { +export default function DeleteButton(props: { data: any; deleteMethod: any }) { const { isOpen, onOpen, onClose } = useDisclosure(); - const deleteDataMutation = useDeleteDataMutation({ - onSuccess(data) { - props.fn(undefined); - }, - }); - return ( <> +
+ {children} + + ); +}; + +export default RightPanelEditBox; diff --git a/web/src/pages/app/database/RightComponent/List.tsx b/web/src/pages/app/database/RightComponent/List.tsx new file mode 100644 index 0000000000..433067d628 --- /dev/null +++ b/web/src/pages/app/database/RightComponent/List.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { EditIcon } from "@chakra-ui/icons"; +import clsx from "clsx"; + +import IconWrap from "@/components/IconWrap"; + +import DeleteButton from "./DeleteButton"; + +const RightPanelList: React.FC<{ + ListQuery?: any; + setKey: string; + isActive: (item: any) => Boolean; + onClick: (data: any) => void; + deleteRuleMutation: any; + deleteData?: (item: any) => any; + component: (item: any) => React.ReactNode; +}> = (props) => { + const { ListQuery, setKey, component, isActive, onClick, deleteRuleMutation, deleteData } = props; + return ( +
+ {(ListQuery || [])?.map((item: any, index: number) => { + return ( +
{ + onClick(item); + }} + > +
+
+ + + + +
+
+ {component(item)} +
+ ); + })} +
+ ); +}; + +export default RightPanelList; diff --git a/web/src/pages/app/database/index.module.scss b/web/src/pages/app/database/index.module.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/pages/app/database/index.tsx b/web/src/pages/app/database/index.tsx index 7661b6131f..3d50d798df 100644 --- a/web/src/pages/app/database/index.tsx +++ b/web/src/pages/app/database/index.tsx @@ -1,31 +1,64 @@ /**************************** * cloud functions database page ***************************/ +import { AddIcon } from "@chakra-ui/icons"; +import { Button } from "@chakra-ui/react"; import Content from "@/components/Content"; import { Col, Row } from "@/components/Grid"; +import Panel from "@/components/Panel"; -import BottomPanel from "./BottomPanel"; +import AddPolicyModal from "./mods/AddPolicyModal"; import CollectionDataList from "./CollectionDataList"; import CollectionListPanel from "./CollectionListPanel"; +import PolicyDataList from "./PolicyDataList"; +import PolicyListPanel from "./PolicyListPanel"; -import useCustomSettingStore from "@/pages/customSetting"; +import useDBMStore from "./store"; +import useCustomSettingStore from "@/pages/customSetting"; function DatabasePage() { - const collectionPageConfig = useCustomSettingStore((store) => store.layoutInfo.collectionPage); + const store = useDBMStore((state) => state); + const settingStore = useCustomSettingStore(); return ( - - + + + + + - - + + {store.currentShow === "DB" ? ( + + ) : store.currentPolicy === undefined ? ( +
+ + + +
+ ) : ( + + )} +
- - + + + + + +
); diff --git a/web/src/pages/app/database/mods/AddPolicyModal/index.tsx b/web/src/pages/app/database/mods/AddPolicyModal/index.tsx new file mode 100644 index 0000000000..a2fc7346f0 --- /dev/null +++ b/web/src/pages/app/database/mods/AddPolicyModal/index.tsx @@ -0,0 +1,147 @@ +import { useState } from "react"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Select, + useDisclosure, + VStack, +} from "@chakra-ui/react"; + +import { useCreatePolicyMutation, useUpdatePolicyMutation } from "../../service"; + +import { useFunctionListQuery } from "@/pages/app/functions/service"; + +const AddPolicyModal = (props: { + children: React.ReactElement; + isEdit?: boolean; + defaultData?: any; +}) => { + type FormData = { + name: string; + injector?: string; + }; + const functionListQuery = useFunctionListQuery({ onSuccess: (data: any) => {} }); + const { isEdit, defaultData = {} } = props; + const { + register, + handleSubmit, + setFocus, + reset, + watch, + formState: { errors }, + } = useForm(defaultData); + + const createPolicyMutation = useCreatePolicyMutation(); + const updatePolicyMutation = useUpdatePolicyMutation(); + + const onSubmit = async (data: any) => { + if (isEdit) { + await updatePolicyMutation.mutateAsync({ name: data.name, injector: data.injector }); + } else { + await createPolicyMutation.mutateAsync({ name: data.name }); + } + onClose(); + reset({}); + }; + + const { isOpen, onOpen, onClose } = useDisclosure(); + const [address, setAddress] = useState(`/proxy/${defaultData?.name || ""}`); + + React.useEffect(() => { + const subscription = watch((value, { name }) => { + if (name === "name") { + setAddress(`/proxy/${value.name}`); + } + }); + return () => subscription.unsubscribe(); + }, [watch, setAddress]); + + return ( + <> + {React.cloneElement(props.children, { + onClick: () => { + onOpen(); + reset(defaultData); + setTimeout(() => setFocus(isEdit ? "injector" : "name"), 0); + }, + })} + + + + + {isEdit ? "编辑策略" : "添加策略"} + + + + + 策略标识 + + {errors.name && errors.name.message} + + + + 入口地址 + + + + {isEdit ? ( + + injector + + + ) : null} + + + + + + + + + ); +}; + +AddPolicyModal.displayName = "AddPolicyModal"; + +export default AddPolicyModal; diff --git a/web/src/pages/app/database/service.ts b/web/src/pages/app/database/service.ts index 49ff3ecc9b..eecff9670d 100644 --- a/web/src/pages/app/database/service.ts +++ b/web/src/pages/app/database/service.ts @@ -6,6 +6,14 @@ import { CollectionControllerCreate, CollectionControllerFindAll, CollectionControllerRemove, + PolicyControllerCreate, + PolicyControllerFindAll, + PolicyControllerRemove, + PolicyControllerUpdate, + PolicyRuleControllerCreate, + PolicyRuleControllerFindAll, + PolicyRuleControllerRemove, + PolicyRuleControllerUpdate, } from "@/apis/v1/apps"; import useDB from "@/hooks/useDB"; import useGlobalStore from "@/pages/globalStore"; @@ -13,6 +21,8 @@ import useGlobalStore from "@/pages/globalStore"; const queryKeys = { useCollectionListQuery: ["useCollectionListQuery"], useEntryDataQuery: (db: string) => ["useEntryDataQuery", db], + usePolicyListQuery: ["usePolicyListQuery"], + useRulesListQuery: (name: string) => ["useRulesListQuery", name], }; export const useCollectionListQuery = (config?: { onSuccess: (data: any) => void }) => { @@ -176,3 +186,144 @@ export const useDeleteDataMutation = (config?: { onSuccess: (data: any) => void }, ); }; + +export const usePolicyListQuery = (onSuccess?: (data: any) => void) => { + return useQuery( + queryKeys.usePolicyListQuery, + () => { + return PolicyControllerFindAll({}); + }, + { + onSuccess: onSuccess, + }, + ); +}; + +export const useCreatePolicyMutation = () => { + const globalStore = useGlobalStore(); + const queryClient = useQueryClient(); + return useMutation( + (values: any) => { + return PolicyControllerCreate(values); + }, + { + onSuccess(data) { + if (data.error) { + globalStore.showError(data.error); + } else { + queryClient.invalidateQueries(queryKeys.usePolicyListQuery); + } + }, + }, + ); +}; + +export const useUpdatePolicyMutation = () => { + const globalStore = useGlobalStore(); + const queryClient = useQueryClient(); + return useMutation( + (values: any) => { + return PolicyControllerUpdate(values); + }, + { + onSuccess(data) { + if (data.error) { + globalStore.showError(data.error); + } else { + queryClient.invalidateQueries(queryKeys.usePolicyListQuery); + } + }, + }, + ); +}; + +export const useDeletePolicyMutation = () => { + const globalStore = useGlobalStore(); + const queryClient = useQueryClient(); + return useMutation( + (values: any) => { + return PolicyControllerRemove({ name: values }); + }, + { + onSuccess(data) { + if (data.error) { + globalStore.showError(data.error); + } else { + queryClient.invalidateQueries(queryKeys.usePolicyListQuery); + } + }, + }, + ); +}; + +export const useRulesListQuery = (onSuccess?: (data: any) => void) => { + const { currentPolicy } = useDBMStore(); + return useQuery( + [queryKeys.useRulesListQuery(currentPolicy?.name || "")], + () => { + return PolicyRuleControllerFindAll({ name: currentPolicy?.name || "" }); + }, + { + onSuccess(data) { + onSuccess && onSuccess(data); + }, + }, + ); +}; + +export const useCreateRulesMutation = (onSuccess?: () => void) => { + const { currentPolicy } = useDBMStore(); + const globalStore = useGlobalStore(); + return useMutation( + (values: any) => { + return PolicyRuleControllerCreate({ ...values, name: currentPolicy?.name || "" }); + }, + { + onSuccess(data) { + if (data.error) { + globalStore.showError(data.error); + } else { + onSuccess && onSuccess(); + } + }, + }, + ); +}; + +export const useUpdateRulesMutation = (onSuccess?: () => void) => { + const { currentPolicy } = useDBMStore(); + const globalStore = useGlobalStore(); + return useMutation( + (values: any) => { + return PolicyRuleControllerUpdate({ ...values, name: currentPolicy?.name || "" }); + }, + { + onSuccess(data) { + if (data.error) { + globalStore.showError(data.error); + } else { + onSuccess && onSuccess(); + } + }, + }, + ); +}; + +export const useDeleteRuleMutation = (onSuccess?: () => void) => { + const { currentPolicy } = useDBMStore(); + const globalStore = useGlobalStore(); + return useMutation( + (values: any) => { + return PolicyRuleControllerRemove({ ...values, name: currentPolicy?.name || "" }); + }, + { + onSuccess(data) { + if (data.error) { + globalStore.showError(data.error); + } else { + onSuccess && onSuccess(); + } + }, + }, + ); +}; diff --git a/web/src/pages/app/database/store.ts b/web/src/pages/app/database/store.ts index 697af5533d..4667b43f68 100644 --- a/web/src/pages/app/database/store.ts +++ b/web/src/pages/app/database/store.ts @@ -5,18 +5,35 @@ import { immer } from "zustand/middleware/immer"; import { TDB } from "@/apis/typing"; type State = { + currentShow: "DB" | "Policy"; currentDB?: TDB | undefined; + currentPolicy?: any; + setCurrentShow: (currentItem: "DB" | "Policy") => void; setCurrentDB: (currentDB: TDB | undefined) => void; + setCurrentPolicy: (currentPolicy: any) => void; }; const useDBMStore = create()( devtools( immer((set) => ({ + currentShow: "DB", currentDB: undefined, - + currentPolicy: undefined, setCurrentDB: async (currentDB: any) => { set((state) => { state.currentDB = currentDB; + state.setCurrentShow("DB"); + }); + }, + setCurrentPolicy: async (currentPolicy: any) => { + set((state) => { + state.currentPolicy = currentPolicy; + state.setCurrentShow("Policy"); + }); + }, + setCurrentShow: async (currentItem: "DB" | "Policy") => { + set((state) => { + state.currentShow = currentItem; }); }, })), diff --git a/web/src/pages/app/functions/mods/BottomPanel/index.tsx b/web/src/pages/app/functions/mods/BottomPanel/index.tsx index faf066c9f5..8292ea0e74 100644 --- a/web/src/pages/app/functions/mods/BottomPanel/index.tsx +++ b/web/src/pages/app/functions/mods/BottomPanel/index.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Button, HStack } from "@chakra-ui/react"; import Panel from "@/components/Panel"; diff --git a/web/src/pages/app/logs/index.module.scss b/web/src/pages/app/logs/index.module.scss new file mode 100644 index 0000000000..1bbdb6fee2 --- /dev/null +++ b/web/src/pages/app/logs/index.module.scss @@ -0,0 +1,11 @@ +.text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.primaryText { + font-weight: 400; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + color: var(--chakra-colors-primary-500); +} \ No newline at end of file diff --git a/web/src/pages/app/logs/index.tsx b/web/src/pages/app/logs/index.tsx index 470f8432b0..a2e8641cf7 100644 --- a/web/src/pages/app/logs/index.tsx +++ b/web/src/pages/app/logs/index.tsx @@ -1,14 +1,12 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; import SyntaxHighlighter from "react-syntax-highlighter"; -import { Search2Icon } from "@chakra-ui/icons"; import { Button, Center, HStack, Input, InputGroup, - InputLeftElement, Modal, ModalBody, ModalCloseButton, @@ -21,8 +19,6 @@ import { TableContainer, Tbody, Td, - Th, - Thead, Tr, useDisclosure, } from "@chakra-ui/react"; @@ -32,12 +28,13 @@ import Content from "@/components/Content"; import CopyText from "@/components/CopyText"; import Pagination from "@/components/Pagination"; import Panel from "@/components/Panel"; -import TextButton from "@/components/TextButton"; import { formatDate } from "@/utils/format"; import getPageInfo from "@/utils/getPageInfo"; import { queryKeys } from "./service"; +import styles from "./index.module.scss"; + import { LogControllerGetLogs } from "@/apis/v1/apps"; const DEFAULT_LIMIT = 20; @@ -45,6 +42,8 @@ const DEFAULT_LIMIT = 20; type TLog = { data: string; request_id: string; + func: string; + created_at: Date; }; export default function LogsPage() { @@ -94,20 +93,14 @@ export default function LogsPage() { - } - />