diff --git a/react-ui/.storybook/main.ts b/react-ui/.storybook/main.ts index 2eff8e2c..d0c13b18 100644 --- a/react-ui/.storybook/main.ts +++ b/react-ui/.storybook/main.ts @@ -16,6 +16,7 @@ const config: StorybookConfig = { name: '@storybook/react-webpack5', options: {}, }, + staticDirs: ['../public'], webpackFinal: async (config) => { if (config.resolve) { config.resolve.alias = { diff --git a/react-ui/.storybook/mock/umijs.mock.tsx b/react-ui/.storybook/mock/umijs.mock.tsx index 66ef1e5c..4f25eeb3 100644 --- a/react-ui/.storybook/mock/umijs.mock.tsx +++ b/react-ui/.storybook/mock/umijs.mock.tsx @@ -5,5 +5,12 @@ export const Link = ({ to, children, ...props }: any) => ( ); export const request = (url: string, options: any) => { - return fetch(url, options).then((res) => res.json()); + return fetch(url, options) + .then((res) => { + if (!res.ok) { + throw new Error(res.statusText); + } + return res; + }) + .then((res) => res.json()); }; diff --git a/react-ui/.storybook/preview.tsx b/react-ui/.storybook/preview.tsx index cbdef6b1..8dc3c3fe 100644 --- a/react-ui/.storybook/preview.tsx +++ b/react-ui/.storybook/preview.tsx @@ -4,8 +4,16 @@ import themes from '@/styles/theme.less'; import type { Preview } from '@storybook/react'; import { App, ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; +import { initialize, mswLoader } from 'msw-storybook-addon'; import './storybook.css'; +/* + * Initializes MSW + * See https://github.com/mswjs/msw-storybook-addon#configuring-msw + * to learn how to customize it + */ +initialize(); + const preview: Preview = { parameters: { controls: { @@ -77,6 +85,7 @@ const preview: Preview = { ), ], + loaders: [mswLoader], // 👈 Add the MSW loader to all stories }; export default preview; diff --git a/react-ui/.storybook/storybook.css b/react-ui/.storybook/storybook.css index 0084b0f8..6c592a3c 100644 --- a/react-ui/.storybook/storybook.css +++ b/react-ui/.storybook/storybook.css @@ -6,7 +6,14 @@ body, margin: 0; padding: 0; overflow-y: visible; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, - 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', - 'Noto Color Emoji'; +} + +.ant-input-search-large .ant-input-affix-wrapper, .ant-input-search-large .ant-input-search-button { + height: 46px; +} + +*, +*::before, +*::after { + box-sizing: border-box; } diff --git a/react-ui/package.json b/react-ui/package.json index e69739ad..c98f0286 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -119,6 +119,8 @@ "less-loader": "~12.2.0", "lint-staged": "^13.2.0", "mockjs": "^1.1.0", + "msw": "~2.7.0", + "msw-storybook-addon": "~2.0.4", "prettier": "^2.8.1", "storybook": "~8.5.3", "swagger-ui-dist": "^4.18.2", @@ -158,5 +160,10 @@ "CNAME", "create-umi" ] + }, + "msw": { + "workerDirectory": [ + "public" + ] } -} +} \ No newline at end of file diff --git a/react-ui/public/mockServiceWorker.js b/react-ui/public/mockServiceWorker.js new file mode 100644 index 00000000..ec47a9a5 --- /dev/null +++ b/react-ui/public/mockServiceWorker.js @@ -0,0 +1,307 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.7.0' +const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/react-ui/src/components/CodeSelectorModal/index.less b/react-ui/src/components/CodeSelectorModal/index.less index cb77da6d..82d73ff8 100644 --- a/react-ui/src/components/CodeSelectorModal/index.less +++ b/react-ui/src/components/CodeSelectorModal/index.less @@ -1,4 +1,4 @@ -.code-selector { +.kf-code-selector-modal { width: 100%; height: 100%; @@ -6,31 +6,6 @@ width: 100%; } - :global { - .ant-input-affix-wrapper { - border-radius: 23px !important; - .ant-input-prefix { - margin-inline-end: 12px; - } - .ant-input-suffix { - margin-inline-end: 12px; - } - .ant-input-clear-icon { - font-size: 16px; - } - } - - .ant-input-group-addon { - display: none; - } - - .ant-pagination { - .ant-select-single { - height: 32px !important; - } - } - } - &__content { display: flex; flex-direction: row; @@ -47,4 +22,28 @@ &__empty { padding-top: 40px; } + + // 覆盖 antd 样式 + .ant-input-affix-wrapper { + border-radius: 23px !important; + .ant-input-prefix { + margin-inline-end: 12px; + } + .ant-input-suffix { + margin-inline-end: 12px; + } + .ant-input-clear-icon { + font-size: 16px; + } + } + + .ant-input-group-addon { + display: none; + } + + .ant-pagination { + .ant-select-single { + height: 32px !important; + } + } } diff --git a/react-ui/src/components/CodeSelectorModal/index.tsx b/react-ui/src/components/CodeSelectorModal/index.tsx index 6a965fd7..6426e8c7 100644 --- a/react-ui/src/components/CodeSelectorModal/index.tsx +++ b/react-ui/src/components/CodeSelectorModal/index.tsx @@ -13,7 +13,7 @@ import type { ModalProps, PaginationProps } from 'antd'; import { Empty, Input, Pagination } from 'antd'; import { useEffect, useState } from 'react'; import CodeConfigItem from '../CodeConfigItem'; -import styles from './index.less'; +import './index.less'; export { type CodeConfigData }; @@ -80,9 +80,9 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { footer={null} destroyOnClose > -
+
{dataList?.length !== 0 ? ( <> -
+
{dataList?.map((item) => ( ))} @@ -116,7 +116,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { /> ) : ( -
+
)} diff --git a/react-ui/src/components/InfoGroup/InfoGroupTitle.less b/react-ui/src/components/InfoGroup/InfoGroupTitle.less index 26ed375a..b8aff97c 100644 --- a/react-ui/src/components/InfoGroup/InfoGroupTitle.less +++ b/react-ui/src/components/InfoGroup/InfoGroupTitle.less @@ -1,5 +1,4 @@ .kf-info-group-title { - box-sizing: border-box; width: 100%; height: 56px; padding: 0 @content-padding; diff --git a/react-ui/src/components/PageTitle/index.tsx b/react-ui/src/components/PageTitle/index.tsx index ea8a65de..2703e032 100644 --- a/react-ui/src/components/PageTitle/index.tsx +++ b/react-ui/src/components/PageTitle/index.tsx @@ -9,7 +9,7 @@ import './index.less'; type PageTitleProps = { /** 标题 */ - title: string; + title: React.ReactNode; /** 自定义类名 */ className?: string; /** 自定义样式 */ diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx index be56c96d..99bb78af 100644 --- a/react-ui/src/components/ParameterInput/index.tsx +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -26,18 +26,34 @@ export type ParameterInputObject = { export type ParameterInputValue = ParameterInputObject | string; export interface ParameterInputProps { + /** 值,可以是字符串,也可以是 ParameterInputObject 对象 */ value?: ParameterInputValue; + /** + * 值变化时的回调 + * @param value 值,可以是字符串,也可以是 ParameterInputObject 对象 + */ onChange?: (value?: ParameterInputValue) => void; + /** 点击时的回调 */ onClick?: () => void; + /** 删除时的回调 */ onRemove?: () => void; + /** 是否可以手动输入 */ canInput?: boolean; + /** 是否是文本框 */ textArea?: boolean; + /** 占位符 */ placeholder?: string; + /** 是否允许清除 */ allowClear?: boolean; + /** 自定义类名 */ className?: string; + /** 自定义样式 */ style?: React.CSSProperties; + /** 大小 */ size?: 'middle' | 'small' | 'large'; + /** 是否禁用 */ disabled?: boolean; + /** 元素 id */ id?: string; } diff --git a/react-ui/src/components/ResourceSelect/index.tsx b/react-ui/src/components/ResourceSelect/index.tsx index 6e0179d4..bc8d08cf 100644 --- a/react-ui/src/components/ResourceSelect/index.tsx +++ b/react-ui/src/components/ResourceSelect/index.tsx @@ -21,14 +21,16 @@ export { requiredValidator, type ParameterInputObject } from '../ParameterInput' export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse }; type ResourceSelectProps = { + /** 类型,数据集、模型、镜像 */ type: ResourceSelectorType; } & ParameterInputProps; -// 获取选择数据集、模型后面按钮 icon +// 获取选择数据集、模型、镜像后面按钮 icon const getSelectBtnIcon = (type: ResourceSelectorType) => { return ; }; +/** 数据集、模型、镜像选择表单组件 */ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSelectProps) { const [selectedResource, setSelectedResource] = useState( undefined, diff --git a/react-ui/src/components/ResourceSelectorModal/index.tsx b/react-ui/src/components/ResourceSelectorModal/index.tsx index 1a62819c..e65f1f02 100644 --- a/react-ui/src/components/ResourceSelectorModal/index.tsx +++ b/react-ui/src/components/ResourceSelectorModal/index.tsx @@ -16,7 +16,7 @@ import { ResourceSelectorType, selectorTypeConfig } from './config'; import styles from './index.less'; export { ResourceSelectorType, selectorTypeConfig }; -// 选择数据集\模型\镜像的返回类型 +// 选择数据集、模型、镜像的返回类型 export type ResourceSelectorResponse = { activeTab: CommonTabKeys; // 是我的还是公开的 id: string; // 数据集\模型\镜像 id @@ -28,10 +28,18 @@ export type ResourceSelectorResponse = { }; export interface ResourceSelectorModalProps extends Omit { - type: ResourceSelectorType; // 数据集\模型\镜像 + /** 类型,数据集、模型、镜像 */ + type: ResourceSelectorType; + /** 默认展开的节点 */ defaultExpandedKeys?: React.Key[]; + /** 默认展开的节点 */ defaultCheckedKeys?: React.Key[]; + /** 默认激活的 Tab */ defaultActiveTab?: CommonTabKeys; + /** + * 确认回调 + * @param params 选择的数据 + */ onOk?: (params: ResourceSelectorResponse | undefined) => void; } @@ -61,6 +69,7 @@ const getIdAndVersion = (versionKey: string) => { }; }; +/** 选择 数据集、模型、镜像 弹框 */ function ResourceSelectorModal({ type, defaultExpandedKeys = [], diff --git a/react-ui/src/global.less b/react-ui/src/global.less index fbbfa34d..9944c70e 100644 --- a/react-ui/src/global.less +++ b/react-ui/src/global.less @@ -5,7 +5,7 @@ body, height: 100%; margin: 0; padding: 0; - overflow-y: hidden; + overflow-y: visible; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; diff --git a/react-ui/src/stories/BasicInfo.stories.tsx b/react-ui/src/stories/BasicInfo.stories.tsx index e6e71255..6fece79a 100644 --- a/react-ui/src/stories/BasicInfo.stories.tsx +++ b/react-ui/src/stories/BasicInfo.stories.tsx @@ -1,20 +1,9 @@ import BasicInfo from '@/components/BasicInfo'; import { formatDate } from '@/utils/date'; +import { formatList } from '@/utils/format'; import type { Meta, StoryObj } from '@storybook/react'; import { Button } from 'antd'; -const formatList = (value: string[] | null | undefined): string => { - if ( - value === undefined || - value === null || - Array.isArray(value) === false || - value.length === 0 - ) { - return '--'; - } - return value.join(','); -}; - // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = { title: 'Components/BasicInfo', diff --git a/react-ui/src/stories/CodeSelect.stories.tsx b/react-ui/src/stories/CodeSelect.stories.tsx index 3f52e159..8be5b620 100644 --- a/react-ui/src/stories/CodeSelect.stories.tsx +++ b/react-ui/src/stories/CodeSelect.stories.tsx @@ -1,5 +1,9 @@ import CodeSelect from '@/components/CodeSelect'; import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Col, Form, Row } from 'antd'; +import { http, HttpResponse } from 'msw'; +import { codeListData } from './mockData'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = { @@ -8,6 +12,13 @@ const meta = { parameters: { // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout // layout: 'centered', + msw: { + handlers: [ + http.get('/api/mmp/codeConfig', () => { + return HttpResponse.json(codeListData); + }), + ], + }, }, // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs tags: ['autodocs'], @@ -16,7 +27,7 @@ const meta = { // backgroundColor: { control: 'color' }, }, // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - // args: { onClick: fn() }, + args: { onChange: fn() }, } satisfies Meta; export default meta; @@ -24,5 +35,22 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args export const Primary: Story = { - args: {}, + render: ({ onChange }) => { + return ( +
+ + + + + + + +
+ ); + }, }; diff --git a/react-ui/src/stories/CodeSelectorModal.stories.tsx b/react-ui/src/stories/CodeSelectorModal.stories.tsx index 2df1b5fa..deab369c 100644 --- a/react-ui/src/stories/CodeSelectorModal.stories.tsx +++ b/react-ui/src/stories/CodeSelectorModal.stories.tsx @@ -4,6 +4,8 @@ import { useArgs } from '@storybook/preview-api'; import type { Meta, StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; import { Button } from 'antd'; +import { http, HttpResponse } from 'msw'; +import { codeListData } from './mockData'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = { @@ -12,15 +14,19 @@ const meta = { parameters: { // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout layout: 'centered', + msw: { + handlers: [ + http.get('/api/mmp/codeConfig', () => { + return HttpResponse.json(codeListData); + }), + ], + }, }, // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs tags: ['autodocs'], // More on argTypes: https://storybook.js.org/docs/api/argtypes argTypes: { // backgroundColor: { control: 'color' }, - title: { - description: '标题', - }, open: { description: '对话框是否可见', }, @@ -37,17 +43,19 @@ export const Primary: Story = { args: { open: false, }, - render: function Render(args) { + render: function Render({ onOk, onCancel, ...args }) { const [{ open }, updateArgs] = useArgs(); function onClick() { updateArgs({ open: true }); } - function onOk() { + function onModalOk(res: any) { updateArgs({ open: false }); + onOk?.(res); } - function onCancel() { + function onModalCancel() { updateArgs({ open: false }); + onCancel?.(); } return ( @@ -55,27 +63,27 @@ export const Primary: Story = { - + ); }, }; -const OpenModalByFunction = () => { - const handleOnChange = () => { - const { close } = openAntdModal(CodeSelectorModal, { - onOk: () => { - close(); - }, - }); - }; - return ( - - ); -}; - export const OpenInFunction: Story = { - render: () => , + render: function Render(args) { + const handleOnChange = () => { + const { close } = openAntdModal(CodeSelectorModal, { + onOk: (res) => { + const { onOk } = args; + onOk?.(res); + close(); + }, + }); + }; + return ( + + ); + }, }; diff --git a/react-ui/src/stories/KFModal.stories.tsx b/react-ui/src/stories/KFModal.stories.tsx index 3efe5e99..208ced34 100644 --- a/react-ui/src/stories/KFModal.stories.tsx +++ b/react-ui/src/stories/KFModal.stories.tsx @@ -25,6 +25,10 @@ const meta = { open: { description: '对话框是否可见', }, + children: { + description: '子元素', + type: 'string', + }, }, // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args args: { onCancel: fn(), onOk: fn() }, @@ -41,17 +45,19 @@ export const Primary: Story = { open: false, children: '这是一个模态框', }, - render: function Render(args) { + render: function Render({ onOk, onCancel, ...args }) { const [{ open }, updateArgs] = useArgs(); function onClick() { updateArgs({ open: true }); } - function onOk() { + function onModalOk() { updateArgs({ open: false }); + onOk?.(); } - function onCancel() { + function onModalCancel() { updateArgs({ open: false }); + onCancel?.(); } return ( @@ -59,30 +65,28 @@ export const Primary: Story = { - + ); }, }; -const OpenModalByFunction = () => { - const handleOnChange = () => { - const { close } = openAntdModal(KFModal, { - title: '创建实验', - image: CreateExperiment, - children: '这是一个模态框', - onOk: () => { - close(); - }, - }); - }; - return ( - - ); -}; - export const OpenInFunction: Story = { - render: () => , + render: function Render() { + const handleOnChange = () => { + const { close } = openAntdModal(KFModal, { + title: '创建实验', + image: CreateExperiment, + children: '这是一个模态框', + onOk: () => { + close(); + }, + }); + }; + return ( + + ); + }, }; diff --git a/react-ui/src/stories/MenuIconSelector.stories.tsx b/react-ui/src/stories/MenuIconSelector.stories.tsx index 45088835..e2950549 100644 --- a/react-ui/src/stories/MenuIconSelector.stories.tsx +++ b/react-ui/src/stories/MenuIconSelector.stories.tsx @@ -34,17 +34,19 @@ export const Primary: Story = { selectedIcon: 'manual-icon', open: false, }, - render: function Render(args) { + render: function Render({ onOk, onCancel, ...args }) { const [{ open, selectedIcon }, updateArgs] = useArgs(); function onClick() { updateArgs({ open: true }); } - function onOk(value: string) { + function onModalOk(value: string) { updateArgs({ selectedIcon: value, open: false }); + onOk?.(value); } - function onCancel() { + function onModalCancel() { updateArgs({ open: false }); + onCancel?.(); } return ( @@ -56,8 +58,8 @@ export const Primary: Story = { {...args} open={open} selectedIcon={selectedIcon} - onOk={onOk} - onCancel={onCancel} + onOk={onModalOk} + onCancel={onModalCancel} /> ); diff --git a/react-ui/src/stories/ParameterInput.stories.tsx b/react-ui/src/stories/ParameterInput.stories.tsx new file mode 100644 index 00000000..9d9525e2 --- /dev/null +++ b/react-ui/src/stories/ParameterInput.stories.tsx @@ -0,0 +1,108 @@ +import ParameterInput, { ParameterInputValue } from '@/components/ParameterInput'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from 'antd'; +import { useState } from 'react'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Components/ParameterInput', + component: ParameterInput, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + // layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + // backgroundColor: { control: 'color' }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + // args: { onClick: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Input: Story = { + args: { + placeholder: '请输入工作目录', + style: { width: 300 }, + canInput: true, + textArea: false, + allowClear: true, + size: 'large', + }, +}; + +export const Select: Story = { + args: { + placeholder: '请输入工作目录', + style: { width: 300 }, + value: 'storybook', + canInput: false, + size: 'large', + }, +}; + +export const SelectWithObjctValue: Story = { + args: { + placeholder: '请输入工作目录', + style: { width: 300 }, + value: { + value: 'storybook', + showValue: 'storybook', + fromSelect: true, + }, + canInput: true, + size: 'large', + }, +}; + +export const Disabled: Story = { + args: { + placeholder: '请输入工作目录', + style: { width: 300 }, + value: { + value: 'storybook', + showValue: 'storybook', + fromSelect: true, + }, + canInput: true, + size: 'large', + disabled: true, + }, +}; + +export const Application: Story = { + args: { + placeholder: '请输入工作目录', + style: { width: 300 }, + canInput: true, + size: 'large', + }, + render: function Render(args) { + const [value, setValue] = useState(''); + + const onClick = () => { + setValue({ + value: 'storybook', + showValue: 'storybook', + fromSelect: true, + }); + }; + return ( + <> + setValue(value)} + > + + + ); + }, +}; diff --git a/react-ui/src/stories/ParameterSelect.stories.tsx b/react-ui/src/stories/ParameterSelect.stories.tsx deleted file mode 100644 index 4a1907ee..00000000 --- a/react-ui/src/stories/ParameterSelect.stories.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import MirrorBasic from '@/assets/img/mirror-basic.png'; -import ParameterSelect from '@/components/ParameterSelect'; -import type { Meta, StoryObj } from '@storybook/react'; - -// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export -const meta = { - title: 'Components/ParameterSelect', - component: ParameterSelect, - parameters: { - // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout - // layout: 'centered', - }, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ['autodocs'], - // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - // backgroundColor: { control: 'color' }, - }, - // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - // args: { onClick: fn() }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -export const Primary: Story = { - args: { - title: '基本信息', - image: MirrorBasic, - }, -}; diff --git a/react-ui/src/stories/ResourceSelect.stories.tsx b/react-ui/src/stories/ResourceSelect.stories.tsx new file mode 100644 index 00000000..b9db1633 --- /dev/null +++ b/react-ui/src/stories/ResourceSelect.stories.tsx @@ -0,0 +1,135 @@ +import ResourceSelect, { + requiredValidator, + ResourceSelectorType, +} from '@/components/ResourceSelect'; +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Col, Form, Row } from 'antd'; +import { http, HttpResponse } from 'msw'; +import { + datasetDetailData, + datasetListData, + datasetVersionData, + mirrorListData, + mirrorVerionData, + modelDetailData, + modelListData, + modelVersionData, +} from './mockData'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Components/ResourceSelect', + component: ResourceSelect, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + // layout: 'centered', + msw: { + handlers: [ + http.get('/api/mmp/newdataset/queryDatasets', () => { + return HttpResponse.json(datasetListData); + }), + http.get('/api/mmp/newdataset/getVersionList', () => { + return HttpResponse.json(datasetVersionData); + }), + http.get('/api/mmp/newdataset/getDatasetDetail', () => { + return HttpResponse.json(datasetDetailData); + }), + http.get('/api/mmp/newmodel/queryModels', () => { + return HttpResponse.json(modelListData); + }), + http.get('/api/mmp/newmodel/getVersionList', () => { + return HttpResponse.json(modelVersionData); + }), + http.get('/api/mmp/newmodel/getModelDetail', () => { + return HttpResponse.json(modelDetailData); + }), + http.get('/api/mmp/image', () => { + return HttpResponse.json(mirrorListData); + }), + http.get('/api/mmp/imageVersion', () => { + return HttpResponse.json(mirrorVerionData); + }), + ], + }, + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + // backgroundColor: { control: 'color' }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onChange: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary: Story = { + args: { + type: ResourceSelectorType.Dataset, + }, + render: ({ onChange }) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + +
+ ); + }, +}; diff --git a/react-ui/src/stories/ResourceSelectorModal.stories.tsx b/react-ui/src/stories/ResourceSelectorModal.stories.tsx new file mode 100644 index 00000000..e2e0ebd2 --- /dev/null +++ b/react-ui/src/stories/ResourceSelectorModal.stories.tsx @@ -0,0 +1,216 @@ +import ResourceSelectorModal, { ResourceSelectorType } from '@/components/ResourceSelectorModal'; +import { openAntdModal } from '@/utils/modal'; +import { useArgs } from '@storybook/preview-api'; +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Button } from 'antd'; +import { http, HttpResponse } from 'msw'; +import { + datasetDetailData, + datasetListData, + datasetVersionData, + mirrorListData, + mirrorVerionData, + modelDetailData, + modelListData, + modelVersionData, +} from './mockData'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Components/ResourceSelectorModal', + component: ResourceSelectorModal, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + // backgroundColor: { control: 'color' }, + open: { + description: '对话框是否可见', + }, + type: { + control: 'select', + options: Object.values(ResourceSelectorType), + }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onCancel: fn(), onOk: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Dataset: Story = { + args: { + type: ResourceSelectorType.Dataset, + open: false, + }, + parameters: { + msw: { + handlers: [ + http.get('/api/mmp/newdataset/queryDatasets', () => { + return HttpResponse.json(datasetListData); + }), + http.get('/api/mmp/newdataset/getVersionList', () => { + return HttpResponse.json(datasetVersionData); + }), + http.get('/api/mmp/newdataset/getDatasetDetail', () => { + return HttpResponse.json(datasetDetailData); + }), + ], + }, + }, + render: function Render({ onOk, onCancel, ...args }) { + const [{ open }, updateArgs] = useArgs(); + function onClick() { + updateArgs({ open: true }); + } + function onModalOk(res: any) { + updateArgs({ open: false }); + onOk?.(res); + } + + function onModalCancel() { + updateArgs({ open: false }); + onCancel?.(); + } + + return ( + <> + + + + ); + }, +}; + +export const Model: Story = { + args: { + type: ResourceSelectorType.Model, + open: false, + }, + parameters: { + msw: { + handlers: [ + http.get('/api/mmp/newmodel/queryModels', () => { + return HttpResponse.json(modelListData); + }), + http.get('/api/mmp/newmodel/getVersionList', () => { + return HttpResponse.json(modelVersionData); + }), + http.get('/api/mmp/newmodel/getModelDetail', () => { + return HttpResponse.json(modelDetailData); + }), + ], + }, + }, + render: function Render({ onOk, onCancel, ...args }) { + const [{ open }, updateArgs] = useArgs(); + function onClick() { + updateArgs({ open: true }); + } + function onModalOk(res: any) { + updateArgs({ open: false }); + onOk?.(res); + } + + function onModalCancel() { + updateArgs({ open: false }); + onCancel?.(); + } + + return ( + <> + + + + ); + }, +}; + +export const Mirror: Story = { + args: { + type: ResourceSelectorType.Mirror, + open: false, + }, + parameters: { + msw: { + handlers: [ + http.get('/api/mmp/image', () => { + return HttpResponse.json(mirrorListData); + }), + http.get('/api/mmp/imageVersion', () => { + return HttpResponse.json(mirrorVerionData); + }), + ], + }, + }, + render: function Render({ onOk, onCancel, ...args }) { + const [{ open }, updateArgs] = useArgs(); + function onClick() { + updateArgs({ open: true }); + } + function onModalOk(res: any) { + updateArgs({ open: false }); + onOk?.(res); + } + + function onModalCancel() { + updateArgs({ open: false }); + onCancel?.(); + } + + return ( + <> + + + + ); + }, +}; + +export const OpenInFunction: Story = { + args: { + type: ResourceSelectorType.Mirror, + }, + parameters: { + msw: { + handlers: [ + http.get('/api/mmp/image', () => { + return HttpResponse.json(mirrorListData); + }), + http.get('/api/mmp/imageVersion', () => { + return HttpResponse.json(mirrorVerionData); + }), + ], + }, + }, + render: function Render(args) { + const handleOnChange = () => { + const { close } = openAntdModal(ResourceSelectorModal, { + type: args.type, + onOk: (res) => { + const { onOk } = args; + onOk?.(res); + close(); + }, + }); + }; + return ( + + ); + }, +}; diff --git a/react-ui/src/stories/mockData.ts b/react-ui/src/stories/mockData.ts new file mode 100644 index 00000000..3d910b06 --- /dev/null +++ b/react-ui/src/stories/mockData.ts @@ -0,0 +1,548 @@ +export const datasetListData = { + msg: '操作成功', + code: 200, + data: { + content: [ + { + name: '手写体识别训练测试数据集', + identifier: 'admin_dataset_20241213140429', + description: '手写体识别数据集', + is_public: false, + time_ago: '2个月前', + id: 1454047, + visits: 0, + create_by: '陈志航', + owner: 'chenzhihang', + }, + { + name: '手写体识别', + identifier: 'admin_dataset_20241213140020', + description: '手写体识别数据集', + is_public: false, + time_ago: '2个月前', + id: 1454046, + visits: 0, + create_by: '陈志航', + owner: 'chenzhihang', + }, + { + name: '生物活性分子数据集', + identifier: 'admin_dataset_20241211151411', + description: '生物活性分子数据集', + is_public: false, + time_ago: '2个月前', + id: 1454004, + visits: 0, + create_by: '陈志航', + owner: 'chenzhihang', + }, + { + name: '介电材料数据集', + identifier: 'admin_dataset_20241211151330', + description: '介电材料数据集', + is_public: false, + time_ago: '2个月前', + id: 1454003, + visits: 0, + create_by: '陈志航', + owner: 'chenzhihang', + }, + ], + pageable: { + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + pageSize: 2000, + pageNumber: 0, + offset: 0, + unpaged: false, + paged: true, + }, + last: true, + totalElements: 4, + totalPages: 1, + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + first: true, + number: 0, + numberOfElements: 4, + size: 2000, + empty: false, + }, +}; + +export const datasetVersionData = { + msg: '操作成功', + code: 200, + data: [ + { + name: 'v2', + http_url: 'https://cdn09022024.gitlink.org.cn/chenzhihang/admin_dataset_20241213140429.git', + zip_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_dataset_20241213140429/archive/v2.zip', + tar_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_dataset_20241213140429/archive/v2.tar.gz', + }, + { + name: 'v3', + http_url: 'https://cdn09022024.gitlink.org.cn/chenzhihang/admin_dataset_20241213140429.git', + zip_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_dataset_20241213140429/archive/v3.zip', + tar_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_dataset_20241213140429/archive/v3.tar.gz', + }, + { + name: 'v1', + http_url: 'https://cdn09022024.gitlink.org.cn/chenzhihang/admin_dataset_20241213140429.git', + zip_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_dataset_20241213140429/archive/v1.zip', + tar_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_dataset_20241213140429/archive/v1.tar.gz', + }, + ], +}; + +export const datasetDetailData = { + msg: '操作成功', + code: 200, + data: { + name: '生物活性分子材料数据集', + identifier: 'admin_dataset_20250217105241', + description: '生物活性分子材料数据集', + is_public: false, + data_type: '自然语言处理', + data_tag: '机器翻译', + version: 'v1', + dataset_version_vos: [ + { + url: '/home/resource/admin/datasets/1454953/admin_dataset_20250217105241/v1/dataset/BBBP.zip', + file_name: 'BBBP.zip', + file_size: '42.14 KB', + }, + ], + id: 1454953, + create_by: '樊帅', + version_desc: '生物活性分子材料数据集', + usage: + '
# 克隆数据集配置文件与存储参数到本地\ngit clone -b v1 https://gitlink.org.cn/fanshuai/admin_dataset_20250217105241.git\n# 远程拉取配置文件\ndvc pull\n
', + update_time: '2025-02-17 10:52:43', + owner: 'fanshuai', + dataset_source: 'add', + relative_paths: 'admin/datasets/1454953/admin_dataset_20250217105241/v1/dataset', + }, +}; + +export const modelListData = { + msg: '操作成功', + code: 200, + data: { + content: [ + { + id: 1454208, + name: '介电材料模型1', + create_by: '陈志航', + description: '介电材料模型1', + time_ago: '2个月前', + owner: 'chenzhihang', + identifier: 'admin_model_20241224095928', + is_public: false, + }, + { + id: 1454007, + name: '手写体识别部署模型', + create_by: '陈志航', + description: '手写体识别部署模型', + time_ago: '2个月前', + owner: 'chenzhihang', + identifier: 'admin_model_20241211151713', + is_public: false, + }, + { + id: 1454006, + name: '生物活性分子材料', + create_by: '陈志航', + description: '生物活性分子材料', + time_ago: '2个月前', + owner: 'chenzhihang', + identifier: 'admin_model_20241211151645', + is_public: false, + }, + { + id: 1454005, + name: '介电材料模型', + create_by: '陈志航', + description: '介电材料模型', + time_ago: '2个月前', + owner: 'chenzhihang', + identifier: 'admin_model_20241211151601', + is_public: false, + }, + ], + pageable: { + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + pageSize: 2000, + pageNumber: 0, + offset: 0, + unpaged: false, + paged: true, + }, + last: true, + totalElements: 4, + totalPages: 1, + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + first: true, + number: 0, + numberOfElements: 4, + size: 2000, + empty: false, + }, +}; + +export const modelVersionData = { + msg: '操作成功', + code: 200, + data: [ + { + name: 'v1', + http_url: 'https://cdn09022024.gitlink.org.cn/chenzhihang/admin_model_20241224095928.git', + zip_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_model_20241224095928/archive/v1.zip', + tar_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_model_20241224095928/archive/v1.tar.gz', + }, + { + name: 'v2', + http_url: 'https://cdn09022024.gitlink.org.cn/chenzhihang/admin_model_20241224095928.git', + zip_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_model_20241224095928/archive/v1.zip', + tar_url: + 'https://www.gitlink.org.cn/api/chenzhihang/admin_model_20241224095928/archive/v1.tar.gz', + }, + ], +}; + +export const modelDetailData = { + msg: '操作成功', + code: 200, + data: { + id: 1454208, + name: '介电材料模型1', + version: 'v1', + version_desc: '介电材料模型1', + create_by: '陈志航', + create_time: '2024-12-24 09:59:31', + update_time: '2024-12-24 09:59:31', + model_size: '101.90 KB', + model_source: 'add', + model_tag: '图像分类', + model_type: 'PyTorch', + description: '介电材料模型1', + usage: + '
# 克隆模型配置文件与存储参数到本地\ngit clone -b v1 https://gitlink.org.cn/chenzhihang/admin_model_20241224095928.git\n# 远程拉取配置文件\ndvc pull\n
', + owner: 'chenzhihang', + identifier: 'admin_model_20241224095928', + is_public: false, + relative_paths: 'admin/model/1454208/admin_model_20241224095928/v1/model', + model_version_vos: [ + { + url: '/home/resource/admin/model/1454208/admin_model_20241224095928/v1/model/sklearn_svr_good.pkl', + file_name: 'sklearn_svr_good.pkl', + file_size: '101.90 KB', + }, + ], + }, +}; + +export const mirrorListData = { + code: 200, + msg: '操作成功', + data: { + content: [ + { + id: 42, + name: 'ccr.ccs.tencentyun.com/somunslotus/httpserver', + description: 'htttp服务器镜像', + image_type: 0, + create_by: 'admin', + create_time: '2024-04-30T14:44:37.000+08:00', + update_by: 'admin', + update_time: '2024-04-30T14:44:37.000+08:00', + state: 1, + version_count: 1, + }, + { + id: 44, + name: 'minio-test', + description: 'minio镜像', + image_type: 0, + create_by: 'admin', + create_time: '2024-05-08T15:19:00.000+08:00', + update_by: 'admin', + update_time: '2024-05-08T15:19:00.000+08:00', + state: 1, + version_count: 1, + }, + { + id: 46, + name: 'machine-learning/mnist-model-deploy', + description: 'mnist手写体识别部署镜像', + image_type: 0, + create_by: 'admin', + create_time: '2024-05-28T10:18:30.000+08:00', + update_by: 'admin', + update_time: '2024-05-28T10:18:30.000+08:00', + state: 1, + version_count: 2, + }, + { + id: 49, + name: 'go_httpsever', + description: 'golang httpserver镜像', + image_type: 0, + create_by: 'admin', + create_time: '2024-06-25T11:11:41.000+08:00', + update_by: 'admin', + update_time: '2024-06-25T11:11:41.000+08:00', + state: 1, + version_count: 1, + }, + { + id: 51, + name: 'pytorch2_python3_cuda_12', + description: 'pytorch2.1.2_python3.11_cuda_12', + image_type: 0, + create_by: 'admin', + create_time: '2024-10-14T16:16:35.000+08:00', + update_by: 'admin', + update_time: '2024-10-14T16:16:35.000+08:00', + state: 1, + version_count: 2, + }, + { + id: 53, + name: 'jupyterlab', + description: 'v1', + image_type: 0, + create_by: 'admin', + create_time: '2024-10-15T16:19:29.000+08:00', + update_by: 'admin', + update_time: '2024-10-15T16:19:29.000+08:00', + state: 1, + version_count: 1, + }, + { + id: 55, + name: 'machine-learning/ax-pytorch', + description: '自动机器学习Ax镜像,带pytorch', + image_type: 0, + create_by: 'admin', + create_time: '2024-12-13T11:25:37.000+08:00', + update_by: 'admin', + update_time: '2024-12-13T11:25:37.000+08:00', + state: 1, + version_count: 1, + }, + ], + pageable: { + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + pageSize: 2000, + pageNumber: 0, + offset: 0, + unpaged: false, + paged: true, + }, + last: true, + totalElements: 7, + totalPages: 1, + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + first: true, + number: 0, + numberOfElements: 7, + size: 2000, + empty: false, + }, +}; + +export const mirrorVerionData = { + code: 200, + msg: '操作成功', + data: { + content: [ + { + id: 54, + image_id: 42, + version: null, + url: '172.20.32.187/testlib/admin/ccr.ccs.tencentyun.com/somunslotus/httpserver:v1', + tag_name: 'v1', + file_size: '6.98 MB', + status: 'available', + create_by: 'admin', + create_time: '2024-04-30T14:44:37.000+08:00', + update_by: 'admin', + update_time: '2024-04-30T14:44:51.000+08:00', + state: 1, + host_ip: null, + }, + ], + pageable: { + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + pageSize: 2000, + pageNumber: 0, + offset: 0, + unpaged: false, + paged: true, + }, + last: true, + totalElements: 1, + totalPages: 1, + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + first: true, + number: 0, + numberOfElements: 1, + size: 2000, + empty: false, + }, +}; + +export const codeListData = { + code: 200, + msg: '操作成功', + data: { + content: [ + { + id: 2, + code_repo_name: '介电材料代码', + code_repo_vis: 1, + git_url: 'https://gitlink.org.cn/fuli/ML_for_Materials.git', + git_branch: 'master', + verify_mode: null, + git_user_name: null, + git_password: null, + ssh_key: null, + create_by: 'admin', + create_time: '2024-10-14T16:10:45.000+08:00', + update_by: 'admin', + update_time: '2024-10-14T16:10:45.000+08:00', + state: 1, + }, + { + id: 3, + code_repo_name: '生物活性材料代码', + code_repo_vis: 1, + git_url: 'https://gitlink.org.cn/zhaoyihan/test_mole_pre.git', + git_branch: 'parse_dataset', + verify_mode: null, + git_user_name: null, + git_password: null, + ssh_key: null, + create_by: 'admin', + create_time: '2024-10-16T08:41:39.000+08:00', + update_by: 'admin', + update_time: '2024-10-16T08:41:39.000+08:00', + state: 1, + }, + { + id: 4, + code_repo_name: '数据处理', + code_repo_vis: 1, + git_url: 'https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git', + git_branch: 'train_ci_test', + verify_mode: null, + git_user_name: null, + git_password: null, + ssh_key: null, + create_by: 'admin', + create_time: '2024-10-16T14:51:18.000+08:00', + update_by: 'admin', + update_time: '2024-10-16T14:51:18.000+08:00', + state: 1, + }, + { + id: 5, + code_repo_name: '手写体识别部署', + code_repo_vis: 1, + git_url: 'https://gitlink.org.cn/somunslotus/mnist-inference.git', + git_branch: 'master', + verify_mode: null, + git_user_name: null, + git_password: null, + ssh_key: null, + create_by: 'admin', + create_time: '2024-10-16T16:36:43.000+08:00', + update_by: 'admin', + update_time: '2024-10-16T16:36:43.000+08:00', + state: 1, + }, + { + id: 7, + code_repo_name: '手写体识别训练', + code_repo_vis: 1, + git_url: 'https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git', + git_branch: 'train_ci_test', + verify_mode: null, + git_user_name: null, + git_password: null, + ssh_key: null, + create_by: 'admin', + create_time: '2024-12-13T13:58:50.000+08:00', + update_by: 'admin', + update_time: '2024-12-13T13:58:50.000+08:00', + state: 1, + }, + ], + pageable: { + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + pageSize: 20, + pageNumber: 0, + offset: 0, + unpaged: false, + paged: true, + }, + last: true, + totalElements: 5, + totalPages: 1, + sort: { + unsorted: true, + sorted: false, + empty: true, + }, + first: true, + number: 0, + numberOfElements: 5, + size: 20, + empty: false, + }, +};