diff --git a/.eslintrc.js b/.eslintrc.js index 1ab82586..a4280fa9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,7 @@ module.exports = { - extends: require.resolve('@umijs/max/eslint') + extends: require.resolve('@umijs/max/eslint'), + // react/no-unstable-nested-components config + rules: { + 'react/no-unstable-nested-components': 'on' + } }; diff --git a/config/routes.ts b/config/routes.ts index 194db9c2..3d31c389 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -35,6 +35,13 @@ export default [ icon: 'CloudServer', component: './nodes' }, + { + name: 'API Keys', + path: '/api-keys', + key: 'apikeys', + icon: 'LockOutlined', + component: './api-keys' + }, { name: 'Users', path: '/users', diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index 8b7b8328..00000000 --- a/src/app.ts +++ /dev/null @@ -1,20 +0,0 @@ -// import { RunTimeLayoutConfig } from '@umijs/max'; - -// 运行时配置 - -// 全局初始化数据配置,用于 Layout 用户信息和权限初始化 -// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate -export async function getInitialState(): Promise<{ name: string }> { - return { name: 'admin' }; -} - -// export const layout: RunTimeLayoutConfig = () => { -// return { -// title: '', -// logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg', - -// menu: { -// locale: true, -// }, -// }; -// }; diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 00000000..6f1c7cbd --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,10 @@ +// 运行时配置 + +// 全局初始化数据配置,用于 Layout 用户信息和权限初始化 +// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate +export async function getInitialState(): Promise<{ name: string }> { + console.log('getInitialState+++++++++++++++'); + return { + name: 'admin' + }; +} diff --git a/src/components/seal-form/components/label-info.less b/src/components/seal-form/components/label-info.less new file mode 100644 index 00000000..7acee796 --- /dev/null +++ b/src/components/seal-form/components/label-info.less @@ -0,0 +1,12 @@ +.label-text { + display: flex; + justify-content: flex-start; + align-items: center; + .note-info { + margin-left: 4px; + } + .star { + position: relative; + top: 2px; + } +} diff --git a/src/components/seal-form/components/label-info.tsx b/src/components/seal-form/components/label-info.tsx new file mode 100644 index 00000000..6db8f0e1 --- /dev/null +++ b/src/components/seal-form/components/label-info.tsx @@ -0,0 +1,35 @@ +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import React from 'react'; +import './label-info.less'; + +interface NoteInfoProps { + required?: boolean; + label: string; + description?: React.ReactNode; +} +const NoteInfo: React.FC = (props) => { + console.log('props+++++++++', props); + const { required, description, label } = props || {}; + return ( + + {label} + + {required && ( + + * + + )} + {description && ( + + + + + + )} + + + ); +}; + +export default NoteInfo; diff --git a/src/components/seal-form/components/wrapper.less b/src/components/seal-form/components/wrapper.less index b164e4ee..c1090fd0 100644 --- a/src/components/seal-form/components/wrapper.less +++ b/src/components/seal-form/components/wrapper.less @@ -60,7 +60,7 @@ pointer-events: all; z-index: 5; &.isfoucs-has-value { - top: 8px; + top: 10px; transition: all 0.2s var(--seal-transition-func); } &.blur-no-value { @@ -68,6 +68,7 @@ transition: all 0.2s var(--seal-transition-func); } } + // ==================== select ================== :global(.ant-select) { display: flex; @@ -84,6 +85,12 @@ } :global(.ant-select-selection-item) { height: @inputheight !important; + padding-block: 5px !important; + line-height: 22px !important; + padding-inline-end: 0 !important; + } + :global(.ant-select-arrow) { + top: 10px; } :global( .ant-select-outlined.ant-select-disabled:not(.ant-select-customize-input) @@ -91,6 +98,9 @@ ) { background-color: transparent; } + :global(.ant-select-selection-search-input) { + height: @inputheight !important; + } // ==================== input ==================== :global(.ant-input), :global(.ant-input-password) { diff --git a/src/components/seal-form/components/wrapper.tsx b/src/components/seal-form/components/wrapper.tsx index aae7d699..b38228ac 100644 --- a/src/components/seal-form/components/wrapper.tsx +++ b/src/components/seal-form/components/wrapper.tsx @@ -1,10 +1,14 @@ import classNames from 'classnames'; +import React from 'react'; +import LabelInfo from './label-info'; import wrapperStyle from './wrapper.less'; interface WrapperProps { children: React.ReactNode; label: string; isFocus: boolean; status?: string; + required?: boolean; + description?: React.ReactNode; className?: string; disabled?: boolean; onClick?: () => void; @@ -17,6 +21,8 @@ const Wrapper: React.FC = ({ status, className, disabled, + description, + required, onClick }) => { return ( @@ -38,7 +44,11 @@ const Wrapper: React.FC = ({ : wrapperStyle['blur-no-value'] )} > - {label} + {children} diff --git a/src/components/seal-form/field-wrapper.tsx b/src/components/seal-form/field-wrapper.tsx new file mode 100644 index 00000000..e39620c4 --- /dev/null +++ b/src/components/seal-form/field-wrapper.tsx @@ -0,0 +1,57 @@ +import classNames from 'classnames'; +import LabelInfo from './components/label-info'; +import wrapperStyle from './components/wrapper.less'; +interface WrapperProps { + children: React.ReactNode; + label: string; + status?: string; + className?: string; + disabled?: boolean; + required?: boolean; + description?: string; + onClick?: () => void; +} + +const Wrapper: React.FC = ({ + children, + label, + status, + className, + disabled, + required, + description +}) => { + return ( +
+ +
+ {children} +
+
+ ); +}; + +export default Wrapper; diff --git a/src/components/seal-form/input-number.tsx b/src/components/seal-form/input-number.tsx index 02606270..0d38b747 100644 --- a/src/components/seal-form/input-number.tsx +++ b/src/components/seal-form/input-number.tsx @@ -7,7 +7,7 @@ import { SealFormItemProps } from './types'; const SealInputNumber: React.FC = ( props ) => { - const { label, placeholder, ...rest } = props; + const { label, placeholder, required, description, ...rest } = props; const [isFocus, setIsFocus] = useState(false); const inputRef = useRef(null); const { status } = Form.Item.useStatus(); @@ -50,6 +50,8 @@ const SealInputNumber: React.FC = ( status={status} label={label || (placeholder as string)} isFocus={isFocus} + required={required} + description={description} disabled={props.disabled} onClick={handleClickWrapper} > diff --git a/src/components/seal-form/input-search.tsx b/src/components/seal-form/input-search.tsx index 25be4bfe..445ebcd6 100644 --- a/src/components/seal-form/input-search.tsx +++ b/src/components/seal-form/input-search.tsx @@ -16,7 +16,7 @@ type OnSearch = ( ) => void; const SealInputSearch: React.FC = (props) => { - const { label, placeholder, ...rest } = props; + const { label, placeholder, required, description, ...rest } = props; const [isFocus, setIsFocus] = useState(false); const inputRef = useRef(null); const { status } = Form.Item.useStatus(); @@ -59,6 +59,8 @@ const SealInputSearch: React.FC = (props) => { status={status} label={label || (placeholder as string)} isFocus={isFocus} + required={required} + description={description} disabled={props.disabled} onClick={handleClickWrapper} > diff --git a/src/components/seal-form/password.tsx b/src/components/seal-form/password.tsx index 1be691ff..5f8022fa 100644 --- a/src/components/seal-form/password.tsx +++ b/src/components/seal-form/password.tsx @@ -5,7 +5,7 @@ import Wrapper from './components/wrapper'; import { SealFormItemProps } from './types'; const SealPassword: React.FC = (props) => { - const { label, placeholder, ...rest } = props; + const { label, placeholder, required, description, ...rest } = props; const [isFocus, setIsFocus] = useState(false); const inputRef = useRef(null); const { status } = Form.Item.useStatus(); @@ -44,6 +44,8 @@ const SealPassword: React.FC = (props) => { status={status} label={label || (placeholder as string)} isFocus={isFocus} + required={required} + description={description} disabled={props.disabled} onClick={handleClickWrapper} > diff --git a/src/components/seal-form/seal-input.tsx b/src/components/seal-form/seal-input.tsx index 629b4acb..5a48d93b 100644 --- a/src/components/seal-form/seal-input.tsx +++ b/src/components/seal-form/seal-input.tsx @@ -17,6 +17,8 @@ const SealTextArea: React.FC = (props) => { onBlur, onInput, style, + required, + description, ...rest } = props; const [isFocus, setIsFocus] = useState(false); @@ -73,6 +75,8 @@ const SealTextArea: React.FC = (props) => { status={status} label={label || (placeholder as string)} isFocus={isFocus} + required={required} + description={description} className="seal-textarea-wrapper" disabled={props.disabled} onClick={handleClickWrapper} @@ -92,7 +96,7 @@ const SealTextArea: React.FC = (props) => { }; const SealInput: React.FC = (props) => { - const { label, placeholder, ...rest } = props; + const { label, placeholder, required, description, ...rest } = props; const [isFocus, setIsFocus] = useState(false); const inputRef = useRef(null); const { status } = Form.Item.useStatus(); @@ -135,6 +139,8 @@ const SealInput: React.FC = (props) => { status={status} label={label || (placeholder as string)} isFocus={isFocus} + required={required} + description={description} disabled={props.disabled} onClick={handleClickWrapper} > diff --git a/src/components/seal-form/seal-select.tsx b/src/components/seal-form/seal-select.tsx index 8855cc29..e927dd7d 100644 --- a/src/components/seal-form/seal-select.tsx +++ b/src/components/seal-form/seal-select.tsx @@ -6,7 +6,8 @@ import Wrapper from './components/wrapper'; import { SealFormItemProps } from './types'; const SealSelect: React.FC = (props) => { - const { label, placeholder, children, ...rest } = props; + const { label, placeholder, children, required, description, ...rest } = + props; const [isFocus, setIsFocus] = useState(false); const inputRef = useRef(null); const { status } = Form.Item.useStatus(); @@ -39,7 +40,9 @@ const SealSelect: React.FC = (props) => { }; const handleOnBlur = (e: any) => { - setIsFocus(false); + if (!props.value) { + setIsFocus(false); + } props.onBlur?.(e); }; @@ -48,6 +51,8 @@ const SealSelect: React.FC = (props) => { status={status} label={label || (placeholder as string)} isFocus={isFocus} + required={required} + description={description} disabled={props.disabled} onClick={handleClickWrapper} > diff --git a/src/components/seal-form/types.ts b/src/components/seal-form/types.ts index 886c92fb..85bbffc8 100644 --- a/src/components/seal-form/types.ts +++ b/src/components/seal-form/types.ts @@ -1,3 +1,7 @@ +import React from 'react'; + export interface SealFormItemProps { label?: string; -} \ No newline at end of file + required?: boolean; + description?: React.ReactNode; +} diff --git a/src/global.less b/src/global.less index bf82dbdf..4588e12f 100644 --- a/src/global.less +++ b/src/global.less @@ -26,6 +26,20 @@ html { .css-var-rf { --ant-font-size: var(--font-size-base); } + .css-var-rg, + .css-var-ri, + .css-var-rh { + --ant-font-size: var(--font-size-base); + &.ant-menu-css-var { + --ant-menu-item-height: 46px; + --ant-menu-item-selected-bg: var(--color-white-1); + --ant-menu-item-border-radius: 24px; + --ant-menu-item-selected-color: var(--ant-color-primary); + --ant-menu-item-hover-bg: var(--color-white-1); + --ant-menu-item-color: var(--color-text-1); + --ant-menu-item-active-bg: var(--color-white-1); + } + } .css-var-r0 { // --ant-control-height: 36px; --ant-font-size-sm: var(--font-size-base); @@ -64,7 +78,9 @@ html { --ant-modal-content-padding: 24px 30px; } - .css-var-rf.ant-menu-css-var { + .css-var-rf.ant-menu-css-var, + .css-var-ri.ant-menu-css-var, + .css-var-rh.ant-menu-css-var { --ant-menu-item-height: 46px; --ant-menu-item-selected-bg: var(--color-white-1); --ant-menu-item-border-radius: 24px; diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx deleted file mode 100644 index f1ef1707..00000000 --- a/src/layouts/BasicLayout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { PageContainer } from '@ant-design/pro-components'; -import { Access, useAccess } from '@umijs/max'; -import { Button } from 'antd'; - -const Dashboard: React.FC = () => { - const access = useAccess(); - return ( - - - - - - ); -}; - -export default Dashboard; \ No newline at end of file diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 5e8b5a3e..1899bf61 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -93,17 +93,25 @@ export default (props: any) => { const formatMessage = undefined; - const runtimeConfig = pluginManager.applyPlugins({ - key: 'layout', - type: 'modify', - logout: true, - initialValue: { - ...initialInfo, - notFound:
not found
- } - }); + // const runtimeConfig = pluginManager.applyPlugins({ + // key: 'layout', + // type: 'modify', + // logout: true, + // initialValue: { + // ...initialInfo, + // notFound:
not found
+ // } + // }); + const runtimeConfig = { + ...initialInfo, + logout: () => { + console.log('logout'); + }, + notFound:
not found
+ }; console.log( 'clientRoute==========2=', + props, clientRoutes, runtimeConfig, initialInfo diff --git a/src/layouts/rightRender.tsx b/src/layouts/rightRender.tsx index ba84ffa7..00fadd7d 100644 --- a/src/layouts/rightRender.tsx +++ b/src/layouts/rightRender.tsx @@ -1,6 +1,5 @@ // @ts-nocheck -// This file is generated by Umi automatically -// DO NOT CHANGE IT MANUALLY! + import avatarImg from '@/assets/images/avatar.png'; import { LogoutOutlined } from '@ant-design/icons'; import { Avatar, Dropdown, Menu, Spin, version } from 'antd'; @@ -11,6 +10,7 @@ export function getRightRenderContent(opts: { initialState: any; setInitialState: any; }) { + console.log('runtimeConfig==', opts.runtimeConfig, opts); if (opts.runtimeConfig.rightRender) { return opts.runtimeConfig.rightRender( opts.initialState, @@ -67,6 +67,20 @@ export function getRightRenderContent(opts: { onClick: () => { opts?.runtimeConfig?.logout?.(opts.initialState); } + }, + { + key: 'theme', + label: <>外观, + onClick: () => { + console.log('theme'); + } + }, + { + key: 'lang', + label: '语言', + onClick: () => { + console.log('lang'); + } } ] }; @@ -96,12 +110,15 @@ export function getRightRenderContent(opts: { return (
{opts.runtimeConfig.logout ? ( - - {avatar} - + <> + + {avatar} + + + ) : ( avatar )} diff --git a/src/pages/api-keys/components/add-apikey.tsx b/src/pages/api-keys/components/add-apikey.tsx new file mode 100644 index 00000000..06e15ec8 --- /dev/null +++ b/src/pages/api-keys/components/add-apikey.tsx @@ -0,0 +1,63 @@ +import ModalFooter from '@/components/modal-footer'; +import SealInput from '@/components/seal-form/seal-input'; +import { PageActionType } from '@/config/types'; +import { CopyOutlined, SyncOutlined } from '@ant-design/icons'; +import { Form, Modal } from 'antd'; + +type AddModalProps = { + title: string; + action: PageActionType; + open: boolean; + onOk: () => void; + onCancel: () => void; +}; +const AddModal: React.FC = ({ + title, + action, + open, + onOk, + onCancel +}) => { + const [form] = Form.useForm(); + const Suffix = ( + + ); + const RenderCopyButton = () => { + return ; + }; + + return ( + } + > +
+ + + + + } + > + +
+
+ ); +}; + +export default AddModal; diff --git a/src/pages/api-keys/index.tsx b/src/pages/api-keys/index.tsx new file mode 100644 index 00000000..41923481 --- /dev/null +++ b/src/pages/api-keys/index.tsx @@ -0,0 +1,246 @@ +import PageTools from '@/components/page-tools'; +import { PageAction } from '@/config'; +import type { PageActionType } from '@/config/types'; +import useTableRowSelection from '@/hooks/use-table-row-selection'; +import useTableSort from '@/hooks/use-table-sort'; +import { + DeleteOutlined, + EditOutlined, + PlusOutlined, + SyncOutlined +} from '@ant-design/icons'; +import { PageContainer } from '@ant-design/pro-components'; +import { Button, Input, Modal, Space, Table, Tooltip, message } from 'antd'; +import { useState } from 'react'; +import AddAPIKeyModal from './components/add-apikey'; +const { Column } = Table; + +const dataSource = [ + { + key: '1', + name: 'local', + secretKey: `auk_uzem...owsa`, + lastusedTime: '2024-05-22 12:20:10', + createTime: '2024-05-20 12:13:25' + }, + { + key: '2', + name: 'dev', + secretKey: `auk_uzem...okwa`, + lastusedTime: '2024-05-19 13:30:22', + createTime: '2024-05-18 10:28:32' + }, + { + key: '3', + name: 'prod', + secretKey: `auk_uzem...uuds`, + lastusedTime: '2024-05-18 10:28:32', + createTime: '2024-05-17 08:21:09' + }, + { + key: '4', + name: 'test', + secretKey: `auk_uzem...uksa`, + lastusedTime: '2024-05-18 10:28:32', + createTime: '2024-05-16 13:33:23' + } +]; + +const Models: React.FC = () => { + const rowSelection = useTableRowSelection(); + const { sortOrder, setSortOrder } = useTableSort({ + defaultSortOrder: 'descend' + }); + const [total, setTotal] = useState(100); + const [openAddModal, setOpenAddModal] = useState(false); + const [loading, setLoading] = useState(false); + const [action, setAction] = useState(PageAction.CREATE); + const [title, setTitle] = useState(''); + const [queryParams, setQueryParams] = useState({ + current: 1, + pageSize: 10, + name: '' + }); + const handleShowSizeChange = (current: number, size: number) => { + console.log(current, size); + }; + + const handlePageChange = (page: number, pageSize: number | undefined) => { + console.log(page, pageSize); + }; + + const handleTableChange = (pagination: any, filters: any, sorter: any) => { + console.log('handleTableChange=======', pagination, filters, sorter); + setSortOrder(sorter.order); + }; + + const fetchData = async () => { + console.log('fetchData'); + }; + const handleSearch = (e: any) => { + fetchData(); + }; + + const handleNameChange = (e: any) => { + setQueryParams({ + ...queryParams, + name: e.target.value + }); + }; + + const handleAddUser = () => { + setOpenAddModal(true); + setAction(PageAction.CREATE); + setTitle('Add User'); + }; + + const handleClickMenu = (e: any) => { + console.log('click', e); + }; + + const handleModalOk = () => { + console.log('handleModalOk'); + setOpenAddModal(false); + }; + + const handleModalCancel = () => { + console.log('handleModalCancel'); + setOpenAddModal(false); + }; + + const handleDelete = () => { + Modal.confirm({ + title: '', + content: 'Are you sure you want to delete the selected keys?', + onOk() { + console.log('OK'); + message.success('successfully!'); + }, + onCancel() { + console.log('Cancel'); + } + }); + }; + + const handleEditUser = () => { + setOpenAddModal(true); + setAction(PageAction.EDIT); + setTitle('Edit User'); + }; + return ( + <> + + + + + + } + right={ + + + + + } + > + + + + + + { + return ( + + + + + + + + + ); + }} + /> +
+
+ + + ); +}; + +export default Models; diff --git a/src/pages/dashboard/components/model-nodes.tsx b/src/pages/dashboard/components/model-nodes.tsx index 54159986..4aa7d72c 100644 --- a/src/pages/dashboard/components/model-nodes.tsx +++ b/src/pages/dashboard/components/model-nodes.tsx @@ -15,9 +15,9 @@ const models = [ const dataSource: NodeItem[] = [ { id: 1, - name: '192.168.1.2', - address: '192.168.1.2', - hostname: 'node-1', + name: 'bj-web-service-1', + address: '183.14.31.136', + hostname: 'bj-web-service-1', labels: {}, resources: { capacity: { @@ -33,13 +33,13 @@ const dataSource: NodeItem[] = [ gram: '24 Gib' } }, - state: 'ALIVE' + state: 'ACTIVE' }, { id: 2, - name: '192.168.1.2', - address: '192.168.1.5', - hostname: 'node-2', + name: 'bj-db-service-2', + address: '172.24.1.36', + hostname: 'bj-db-service-2', labels: {}, resources: { capacity: { @@ -55,29 +55,51 @@ const dataSource: NodeItem[] = [ gram: '12 Gib' } }, - state: 'ALIVE' + state: 'ACTIVE' }, { id: 3, - name: '192.168.1.3', - address: '192.168.1.3', - hostname: 'node-3', + name: 'guangzhou-computed-node-2', + address: '170.10.2.10', + hostname: 'guangzhou-computed-node-2', labels: {}, resources: { capacity: { - cpu: 4, - gpu: 2, + cpu: 8, + gpu: 4, memory: '64 GiB', gram: '24 Gib' }, allocable: { - cpu: 1, - gpu: 0.5, - memory: '28 GiB', - gram: '6 Gib' + cpu: 2, + gpu: 1.5, + memory: '32 GiB', + gram: '12 Gib' } }, - state: 'ALIVE' + state: 'ACTIVE' + }, + { + id: 4, + name: 'hangzhou-cache-node-1', + address: '115.2.21.10', + hostname: 'hangzhou-cache-node-1', + labels: {}, + resources: { + capacity: { + cpu: 8, + gpu: 4, + memory: '64 GiB', + gram: '24 Gib' + }, + allocable: { + cpu: 4, + gpu: 2.5, + memory: '40 GiB', + gram: '16 Gib' + } + }, + state: 'ACTIVE' } ]; diff --git a/src/pages/llm-models/components/add-modal.tsx b/src/pages/llm-models/components/add-modal.tsx index 6c8410b9..44e6e0d0 100644 --- a/src/pages/llm-models/components/add-modal.tsx +++ b/src/pages/llm-models/components/add-modal.tsx @@ -1,8 +1,9 @@ import ModalFooter from '@/components/modal-footer'; +import FieldWrapper from '@/components/seal-form/field-wrapper'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageActionType } from '@/config/types'; -import { Form, Modal } from 'antd'; +import { Form, Modal, Slider } from 'antd'; type AddModalProps = { title: string; @@ -11,6 +12,11 @@ type AddModalProps = { onOk: () => void; onCancel: () => void; }; + +const sourceOptions = [ + { label: 'Huggingface', value: 'huggingface' }, + { label: 'S3', value: 's3' } +]; const AddModal: React.FC = (props) => { const { title, action, open, onOk, onCancel } = props || {}; if (!open) { @@ -33,14 +39,27 @@ const AddModal: React.FC = (props) => { footer={} >
- - - - - - - + + + + + + + + + + + +
diff --git a/src/pages/llm-models/index.tsx b/src/pages/llm-models/index.tsx index 38f532cc..31c9a717 100644 --- a/src/pages/llm-models/index.tsx +++ b/src/pages/llm-models/index.tsx @@ -30,17 +30,19 @@ const dataSource = [ { key: '1', name: 'llama3:latest', - createTime: '2021-09-01 12:00:00' + progress: 30, + transition: true, + createTime: '2024-05-22 12:20:10' }, { key: '2', - name: 'meta-llama/Meta-Llama-3-8B-nksrlsl-esLscs-1:latest', - createTime: '2021-09-01 12:00:00' + name: 'openbmb/MiniCPM-Llama3-V-2_5', + createTime: '2024-05-19 13:30:22' }, { key: '3', - name: 'apple/OpenELM-3B-Instructor', - createTime: '2021-09-01 12:00:00' + name: 'openbmb/MiniCPM-Llama3-V-2_5', + createTime: '2024-05-18 10:28:32' } ]; @@ -92,7 +94,7 @@ const Models: React.FC = () => { const handleAddModal = () => { setOpenAddModal(true); setAction(PageAction.CREATE); - setTitle('Import Module'); + setTitle('Deploy Model'); }; const handleClickMenu = (e: any) => { @@ -197,10 +199,12 @@ const Models: React.FC = () => { return ( <> {text} - + {record.progress && ( + + )} ); }} @@ -218,7 +222,7 @@ const Models: React.FC = () => { title="Operation" key="operation" render={(text, record) => { - return ( + return !record.transition ? ( - ); + ) : null; }} /> diff --git a/src/pages/nodes/components/render-progress.tsx b/src/pages/nodes/components/render-progress.tsx new file mode 100644 index 00000000..05af3f9f --- /dev/null +++ b/src/pages/nodes/components/render-progress.tsx @@ -0,0 +1,53 @@ +import { Progress } from 'antd'; +import _ from 'lodash'; +import { memo, useMemo } from 'react'; +import { NodeItem } from '../config/types'; + +const RenderProgress = memo( + (props: { record: NodeItem; dataIndex: string }) => { + const { record, dataIndex } = props; + const value1 = useMemo(() => { + let value = _.get(record, ['resources', 'allocable', dataIndex]); + if (['gram', 'memory'].includes(dataIndex)) { + value = _.toNumber(value.replace(/GiB|Gib/, '')); + } + return value; + }, [record, dataIndex]); + + const value2 = useMemo(() => { + let value = _.get(record, ['resources', 'capacity', dataIndex]); + if (['gram', 'memory'].includes(dataIndex)) { + value = _.toNumber(value.replace(/GiB|Gib/, '')); + } + return value; + }, [record, dataIndex]); + + if (!value1 || !value2) { + return ; + } + const percent = _.round(value1 / value2, 2) * 100; + const strokeColor = useMemo(() => { + if (percent <= 50) { + return 'var(--ant-color-primary)'; + } + if (percent <= 80) { + return 'var(--ant-color-warning)'; + } + return 'var(--ant-color-error)'; + }, [percent]); + return ( + { + return ( + {percent}% + ); + }} + percent={percent} + strokeColor={strokeColor} + /> + ); + } +); + +export default RenderProgress; diff --git a/src/pages/nodes/index.tsx b/src/pages/nodes/index.tsx index f7e5fd94..6b9cf30a 100644 --- a/src/pages/nodes/index.tsx +++ b/src/pages/nodes/index.tsx @@ -4,18 +4,18 @@ import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableSort from '@/hooks/use-table-sort'; import { SyncOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; -import { Button, Input, Progress, Space, Table } from 'antd'; -import _ from 'lodash'; -import { memo, useMemo, useState } from 'react'; +import { Button, Input, Space, Table } from 'antd'; +import { useState } from 'react'; +import RenderProgress from './components/render-progress'; import { NodeItem } from './config/types'; const { Column } = Table; const dataSource: NodeItem[] = [ { id: 1, - name: '192.168.1.2', - address: '192.168.1.2', - hostname: 'node-1', + name: 'bj-web-service-1', + address: '183.14.31.136', + hostname: 'bj-web-service-1', labels: {}, resources: { capacity: { @@ -31,13 +31,13 @@ const dataSource: NodeItem[] = [ gram: '24 Gib' } }, - state: 'ALIVE' + state: 'ACTIVE' }, { id: 2, - name: '192.168.1.2', - address: '192.168.1.5', - hostname: 'node-2', + name: 'bj-db-service-2', + address: '172.24.1.36', + hostname: 'bj-db-service-2', labels: {}, resources: { capacity: { @@ -53,7 +53,51 @@ const dataSource: NodeItem[] = [ gram: '12 Gib' } }, - state: 'ALIVE' + state: 'ACTIVE' + }, + { + id: 3, + name: 'guangzhou-computed-node-2', + address: '170.10.2.10', + hostname: 'guangzhou-computed-node-2', + labels: {}, + resources: { + capacity: { + cpu: 8, + gpu: 4, + memory: '64 GiB', + gram: '24 Gib' + }, + allocable: { + cpu: 2, + gpu: 1.5, + memory: '32 GiB', + gram: '12 Gib' + } + }, + state: 'ACTIVE' + }, + { + id: 4, + name: 'hangzhou-cache-node-1', + address: '115.2.21.10', + hostname: 'hangzhou-cache-node-1', + labels: {}, + resources: { + capacity: { + cpu: 8, + gpu: 4, + memory: '64 GiB', + gram: '24 Gib' + }, + allocable: { + cpu: 4, + gpu: 2.5, + memory: '40 GiB', + gram: '16 Gib' + } + }, + state: 'ACTIVE' } ]; @@ -62,7 +106,7 @@ const Models: React.FC = () => { const { sortOrder, setSortOrder } = useTableSort({ defaultSortOrder: 'descend' }); - const [total, setTotal] = useState(100); + const [total, setTotal] = useState(10); const [loading, setLoading] = useState(false); const [queryParams, setQueryParams] = useState({ current: 1, @@ -95,53 +139,6 @@ const Models: React.FC = () => { }); }; - const RenderProgress = memo( - (props: { record: NodeItem; dataIndex: string }) => { - const { record, dataIndex } = props; - const value1 = useMemo(() => { - let value = _.get(record, ['resources', 'allocable', dataIndex]); - if (['gram', 'memory'].includes(dataIndex)) { - value = _.toNumber(value.replace(/GiB|Gib/, '')); - } - return value; - }, [record, dataIndex]); - - const value2 = useMemo(() => { - let value = _.get(record, ['resources', 'capacity', dataIndex]); - if (['gram', 'memory'].includes(dataIndex)) { - value = _.toNumber(value.replace(/GiB|Gib/, '')); - } - return value; - }, [record, dataIndex]); - - if (!value1 || !value2) { - return ; - } - const percent = _.round(value1 / value2, 2) * 100; - const strokeColor = useMemo(() => { - if (percent <= 50) { - return 'var(--ant-color-primary)'; - } - if (percent <= 80) { - return 'var(--ant-color-warning)'; - } - return 'var(--ant-color-error)'; - }, [percent]); - return ( - { - return ( - {percent}% - ); - }} - percent={percent} - strokeColor={strokeColor} - /> - ); - } - ); - return ( <>