chore: api request
This commit is contained in:
@@ -1,7 +1,14 @@
|
|||||||
import { defineConfig } from '@umijs/max';
|
import { defineConfig } from '@umijs/max';
|
||||||
|
import proxy from './proxy';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
|
const { REACT_APP_ENV = 'dev' } = process.env;
|
||||||
|
|
||||||
|
console.log('env+++++++++++++++', process.env.NODE_ENV, REACT_APP_ENV);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
proxy: {
|
||||||
|
...proxy()
|
||||||
|
},
|
||||||
clickToComponent: {},
|
clickToComponent: {},
|
||||||
antd: {
|
antd: {
|
||||||
style: 'less',
|
style: 'less',
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const proxyTableList = ['cli', 'v1'];
|
||||||
|
|
||||||
|
// @ts-ingore
|
||||||
|
export default function createProxyTable(target?: string) {
|
||||||
|
const proxyTable = proxyTableList.reduce(
|
||||||
|
(obj: Record<string, object>, api) => {
|
||||||
|
const newTarget = target || 'http://localhost';
|
||||||
|
obj[`/${api}/`] = {
|
||||||
|
target: newTarget,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
pathRewrite: (pth: string) => pth.replace(`/^/${api}/`, `/${api}`),
|
||||||
|
headers: {
|
||||||
|
origin: newTarget
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return proxyTable;
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"antd-style": "^3.6.2",
|
"antd-style": "^3.6.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"numeral": "^2.0.6"
|
"numeral": "^2.0.6"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+3
@@ -35,6 +35,9 @@ dependencies:
|
|||||||
crypto-js:
|
crypto-js:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.11
|
||||||
|
version: 1.11.11
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { RequestConfig } from '@umijs/max';
|
||||||
|
import { requestConfig } from './request-config';
|
||||||
// 运行时配置
|
// 运行时配置
|
||||||
|
|
||||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||||
@@ -8,3 +10,8 @@ export async function getInitialState(): Promise<{ name: string }> {
|
|||||||
name: 'admin'
|
name: 'admin'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const request: RequestConfig = {
|
||||||
|
baseURL: ' /v1',
|
||||||
|
...requestConfig
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
import { Button, Space } from 'antd';
|
import { Button, Space } from 'antd';
|
||||||
|
|
||||||
type ModalFooterProps = {
|
type ModalFooterProps = {
|
||||||
onOk: () => void;
|
onOk?: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
okText?: string;
|
okText?: string;
|
||||||
|
htmlType?: 'button' | 'submit';
|
||||||
|
form?: any;
|
||||||
};
|
};
|
||||||
const ModalFooter: React.FC<ModalFooterProps> = ({
|
const ModalFooter: React.FC<ModalFooterProps> = ({
|
||||||
onOk,
|
onOk,
|
||||||
onCancel,
|
onCancel,
|
||||||
cancelText,
|
cancelText,
|
||||||
okText
|
okText,
|
||||||
|
htmlType = 'button',
|
||||||
|
form
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Space size={20}>
|
<Space size={20}>
|
||||||
<Button onClick={onCancel} style={{ width: '88px' }}>
|
<Button onClick={onCancel} style={{ width: '88px' }}>
|
||||||
{cancelText || '取消'}
|
{cancelText || '取消'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" onClick={onOk} style={{ width: '88px' }}>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={onOk}
|
||||||
|
style={{ width: '88px' }}
|
||||||
|
htmlType={htmlType}
|
||||||
|
>
|
||||||
{okText || '保存'}
|
{okText || '保存'}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import { useEffect, useState } from 'react';
|
|||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
export const StatusMaps = {
|
export const StatusMaps = {
|
||||||
running: 'blue',
|
transitioning: 'blue',
|
||||||
error: 'red',
|
error: 'red',
|
||||||
warning: 'orange',
|
warning: 'orange',
|
||||||
success: 'success'
|
success: 'success',
|
||||||
|
inactive: 'inactive'
|
||||||
};
|
};
|
||||||
|
|
||||||
type StatusTagProps = {
|
type StatusTagProps = {
|
||||||
|
|||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
declare namespace Global {
|
||||||
|
interface Pagination {
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
watch?: boolean;
|
||||||
|
}
|
||||||
|
interface PageResponse<T> {
|
||||||
|
items: T[];
|
||||||
|
pagination: {
|
||||||
|
total: number;
|
||||||
|
totalPage: number;
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
export const NODES_API = '/nodes';
|
||||||
|
|
||||||
|
export async function queryNodesList(
|
||||||
|
params: Global.Pagination & { query?: string }
|
||||||
|
) {
|
||||||
|
return request<Global.PageResponse<ListItem>>(`${NODES_API}`, {
|
||||||
|
methos: 'GET',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,11 +5,11 @@ import useTableSort from '@/hooks/use-table-sort';
|
|||||||
import { SyncOutlined } from '@ant-design/icons';
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
import { Button, Input, Space, Table } from 'antd';
|
import { Button, Input, Space, Table } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { NodeItem } from '../config/types';
|
import { Gpu } from '../config/types';
|
||||||
import RenderProgress from './render-progress';
|
import RenderProgress from './render-progress';
|
||||||
const { Column } = Table;
|
const { Column } = Table;
|
||||||
|
|
||||||
const dataSource: NodeItem[] = [
|
const dataSource: Gpu[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'bj-web-service-1',
|
name: 'bj-web-service-1',
|
||||||
@@ -161,7 +161,6 @@ const Models: React.FC = () => {
|
|||||||
></PageTools>
|
></PageTools>
|
||||||
<Table
|
<Table
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
rowSelection={rowSelection}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
@@ -193,7 +192,11 @@ const Models: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Column title="IP" dataIndex="address" key="address" />
|
<Column title="IP" dataIndex="address" key="address" />
|
||||||
|
|
||||||
<Column title="Temperature" dataIndex="Temperature" key="Temperature" />
|
<Column
|
||||||
|
title="Temperature(˚C)"
|
||||||
|
dataIndex="Temperature"
|
||||||
|
key="Temperature"
|
||||||
|
/>
|
||||||
<Column title="Core" dataIndex="core" key="Core" />
|
<Column title="Core" dataIndex="core" key="Core" />
|
||||||
<Column title="GPU-Util" dataIndex="gpuUtil" key="gpuUtil" />
|
<Column title="GPU-Util" dataIndex="gpuUtil" key="gpuUtil" />
|
||||||
|
|
||||||
@@ -201,10 +204,8 @@ const Models: React.FC = () => {
|
|||||||
title="VRAM"
|
title="VRAM"
|
||||||
dataIndex="GRAM"
|
dataIndex="GRAM"
|
||||||
key="VRAM"
|
key="VRAM"
|
||||||
render={(text, record: NodeItem) => {
|
render={(text, record: Gpu) => {
|
||||||
return (
|
return <RenderProgress percent={0}></RenderProgress>;
|
||||||
<RenderProgress record={record} dataIndex="gram"></RenderProgress>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
|
|||||||
@@ -4,129 +4,59 @@ import useTableRowSelection from '@/hooks/use-table-row-selection';
|
|||||||
import useTableSort from '@/hooks/use-table-sort';
|
import useTableSort from '@/hooks/use-table-sort';
|
||||||
import { SyncOutlined } from '@ant-design/icons';
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
import { Button, Input, Space, Table } from 'antd';
|
import { Button, Input, Space, Table } from 'antd';
|
||||||
import { useState } from 'react';
|
import _ from 'lodash';
|
||||||
import { NodeItem } from '../config/types';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { queryNodesList } from '../apis';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
import RenderProgress from './render-progress';
|
import RenderProgress from './render-progress';
|
||||||
const { Column } = Table;
|
const { Column } = Table;
|
||||||
|
|
||||||
const dataSource: NodeItem[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'bj-web-service-1',
|
|
||||||
address: '183.14.31.136',
|
|
||||||
hostname: 'bj-web-service-1',
|
|
||||||
labels: {},
|
|
||||||
resources: {
|
|
||||||
capacity: {
|
|
||||||
cpu: 4,
|
|
||||||
gpu: 2,
|
|
||||||
memory: '64 GiB',
|
|
||||||
gram: '24 Gib'
|
|
||||||
},
|
|
||||||
allocable: {
|
|
||||||
cpu: 2.5,
|
|
||||||
gpu: 1.6,
|
|
||||||
memory: '64',
|
|
||||||
gram: '24 Gib'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state: 'ACTIVE'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'bj-db-service-2',
|
|
||||||
address: '172.24.1.36',
|
|
||||||
hostname: 'bj-db-service-2',
|
|
||||||
labels: {},
|
|
||||||
resources: {
|
|
||||||
capacity: {
|
|
||||||
cpu: 4,
|
|
||||||
gpu: 2,
|
|
||||||
memory: '64 GiB',
|
|
||||||
gram: '24 Gib'
|
|
||||||
},
|
|
||||||
allocable: {
|
|
||||||
cpu: 2,
|
|
||||||
gpu: 1.5,
|
|
||||||
memory: '32 GiB',
|
|
||||||
gram: '12 Gib'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const Models: React.FC = () => {
|
const Models: React.FC = () => {
|
||||||
const rowSelection = useTableRowSelection();
|
const rowSelection = useTableRowSelection();
|
||||||
const { sortOrder, setSortOrder } = useTableSort({
|
const { sortOrder, setSortOrder } = useTableSort({
|
||||||
defaultSortOrder: 'descend'
|
defaultSortOrder: 'descend'
|
||||||
});
|
});
|
||||||
const [total, setTotal] = useState(10);
|
const [total, setTotal] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [dataSource, setDataSource] = useState<ListItem[]>([]);
|
||||||
const [queryParams, setQueryParams] = useState({
|
const [queryParams, setQueryParams] = useState({
|
||||||
current: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
perPage: 10,
|
||||||
name: ''
|
query: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
..._.pickBy(queryParams, (val: any) => !!val)
|
||||||
|
};
|
||||||
|
const res = await queryNodesList(params);
|
||||||
|
console.log('res=======', res);
|
||||||
|
setDataSource(res.items);
|
||||||
|
setTotal(res.pagination.total);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
const handleShowSizeChange = (current: number, size: number) => {
|
const handleShowSizeChange = (current: number, size: number) => {
|
||||||
console.log(current, size);
|
console.log(current, size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageChange = (page: number, pageSize: number | undefined) => {
|
const handlePageChange = (page: number, perPage: number | undefined) => {
|
||||||
console.log(page, pageSize);
|
console.log(page, perPage);
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
page: page
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||||
setSortOrder(sorter.order);
|
setSortOrder(sorter.order);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
console.log('fetchData');
|
|
||||||
};
|
|
||||||
const handleSearch = (e: any) => {
|
const handleSearch = (e: any) => {
|
||||||
fetchData();
|
fetchData();
|
||||||
};
|
};
|
||||||
@@ -134,10 +64,20 @@ const Models: React.FC = () => {
|
|||||||
const handleNameChange = (e: any) => {
|
const handleNameChange = (e: any) => {
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
name: e.target.value
|
query: e.target.value
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formateUtilazation = (val1: number, val2: number): number => {
|
||||||
|
if (!val2 || !val1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _.round((val1 / val2) * 100, 2);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [queryParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTools
|
<PageTools
|
||||||
@@ -161,14 +101,13 @@ const Models: React.FC = () => {
|
|||||||
></PageTools>
|
></PageTools>
|
||||||
<Table
|
<Table
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
rowSelection={rowSelection}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
pageSize: 10,
|
pageSize: queryParams.perPage,
|
||||||
current: 2,
|
current: queryParams.page,
|
||||||
total: total,
|
total: total,
|
||||||
hideOnSinglePage: true,
|
hideOnSinglePage: true,
|
||||||
onShowSizeChange: handleShowSizeChange,
|
onShowSizeChange: handleShowSizeChange,
|
||||||
@@ -180,12 +119,13 @@ const Models: React.FC = () => {
|
|||||||
title="State"
|
title="State"
|
||||||
dataIndex="state"
|
dataIndex="state"
|
||||||
key="state"
|
key="state"
|
||||||
render={(text, record) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<StatusTag
|
<StatusTag
|
||||||
statusValue={{
|
statusValue={{
|
||||||
status: 'success',
|
status:
|
||||||
text: 'ALIVE'
|
record.status?.state === 'active' ? 'success' : 'error',
|
||||||
|
text: record.status?.state
|
||||||
}}
|
}}
|
||||||
></StatusTag>
|
></StatusTag>
|
||||||
);
|
);
|
||||||
@@ -196,9 +136,11 @@ const Models: React.FC = () => {
|
|||||||
title="CPU"
|
title="CPU"
|
||||||
dataIndex="CPU"
|
dataIndex="CPU"
|
||||||
key="CPU"
|
key="CPU"
|
||||||
render={(text, record: NodeItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<RenderProgress record={record} dataIndex="cpu"></RenderProgress>
|
<RenderProgress
|
||||||
|
percent={_.round(record?.status?.cpu.utilization_rate, 2)}
|
||||||
|
></RenderProgress>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -206,11 +148,13 @@ const Models: React.FC = () => {
|
|||||||
title="Memory"
|
title="Memory"
|
||||||
dataIndex="memory"
|
dataIndex="memory"
|
||||||
key="Memory"
|
key="Memory"
|
||||||
render={(text, record: NodeItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<RenderProgress
|
<RenderProgress
|
||||||
record={record}
|
percent={formateUtilazation(
|
||||||
dataIndex="memory"
|
record?.status?.memory.used,
|
||||||
|
record?.status?.memory.total
|
||||||
|
)}
|
||||||
></RenderProgress>
|
></RenderProgress>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -219,20 +163,16 @@ const Models: React.FC = () => {
|
|||||||
title="GPU"
|
title="GPU"
|
||||||
dataIndex="GPU"
|
dataIndex="GPU"
|
||||||
key="GPU"
|
key="GPU"
|
||||||
render={(text, record: NodeItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return <RenderProgress percent={0}></RenderProgress>;
|
||||||
<RenderProgress record={record} dataIndex="gpu"></RenderProgress>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="VRAM"
|
title="VRAM"
|
||||||
dataIndex="GRAM"
|
dataIndex="GRAM"
|
||||||
key="VRAM"
|
key="VRAM"
|
||||||
render={(text, record: NodeItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return <RenderProgress percent={0}></RenderProgress>;
|
||||||
<RenderProgress record={record} dataIndex="gram"></RenderProgress>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
|
|||||||
@@ -1,53 +1,50 @@
|
|||||||
import { Progress } from 'antd';
|
import { Progress } from 'antd';
|
||||||
import _ from 'lodash';
|
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { NodeItem } from '../config/types';
|
|
||||||
|
|
||||||
const RenderProgress = memo(
|
const RenderProgress = memo((props: { percent: number }) => {
|
||||||
(props: { record: NodeItem; dataIndex: string }) => {
|
const { percent } = props;
|
||||||
const { record, dataIndex } = props;
|
// const { record, dataIndex } = props;
|
||||||
const value1 = useMemo(() => {
|
// const value1 = useMemo(() => {
|
||||||
let value = _.get(record, ['resources', 'allocable', dataIndex]);
|
// let value = _.get(record, ['resources', 'allocable', dataIndex]);
|
||||||
if (['gram', 'memory'].includes(dataIndex)) {
|
// if (['gram', 'memory'].includes(dataIndex)) {
|
||||||
value = _.toNumber(value.replace(/GiB|Gib/, ''));
|
// value = _.toNumber(value.replace(/GiB|Gib/, ''));
|
||||||
}
|
// }
|
||||||
return value;
|
// return value;
|
||||||
}, [record, dataIndex]);
|
// }, [record, dataIndex]);
|
||||||
|
|
||||||
const value2 = useMemo(() => {
|
// const value2 = useMemo(() => {
|
||||||
let value = _.get(record, ['resources', 'capacity', dataIndex]);
|
// let value = _.get(record, ['resources', 'capacity', dataIndex]);
|
||||||
if (['gram', 'memory'].includes(dataIndex)) {
|
// if (['gram', 'memory'].includes(dataIndex)) {
|
||||||
value = _.toNumber(value.replace(/GiB|Gib/, ''));
|
// value = _.toNumber(value.replace(/GiB|Gib/, ''));
|
||||||
}
|
// }
|
||||||
return value;
|
// return value;
|
||||||
}, [record, dataIndex]);
|
// }, [record, dataIndex]);
|
||||||
|
|
||||||
if (!value1 || !value2) {
|
// if (!value1 || !value2) {
|
||||||
return <Progress percent={0} strokeColor="var(--ant-color-primary)" />;
|
// return <Progress percent={0} strokeColor="var(--ant-color-primary)" />;
|
||||||
|
// }
|
||||||
|
// const percent = _.round(value1 / value2, 2) * 100;
|
||||||
|
const strokeColor = useMemo(() => {
|
||||||
|
if (percent <= 50) {
|
||||||
|
return 'var(--ant-color-primary)';
|
||||||
}
|
}
|
||||||
const percent = _.round(value1 / value2, 2) * 100;
|
if (percent <= 80) {
|
||||||
const strokeColor = useMemo(() => {
|
return 'var(--ant-color-warning)';
|
||||||
if (percent <= 50) {
|
}
|
||||||
return 'var(--ant-color-primary)';
|
return 'var(--ant-color-error)';
|
||||||
}
|
}, [percent]);
|
||||||
if (percent <= 80) {
|
return (
|
||||||
return 'var(--ant-color-warning)';
|
<Progress
|
||||||
}
|
steps={5}
|
||||||
return 'var(--ant-color-error)';
|
format={() => {
|
||||||
}, [percent]);
|
return (
|
||||||
return (
|
<span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span>
|
||||||
<Progress
|
);
|
||||||
steps={5}
|
}}
|
||||||
format={() => {
|
percent={percent}
|
||||||
return (
|
strokeColor={strokeColor}
|
||||||
<span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
});
|
||||||
percent={percent}
|
|
||||||
strokeColor={strokeColor}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default RenderProgress;
|
export default RenderProgress;
|
||||||
|
|||||||
@@ -1,19 +1,68 @@
|
|||||||
export interface Capacity {
|
export interface Gpu {
|
||||||
cpu: number;
|
uuid: string;
|
||||||
gpu: number;
|
name: string;
|
||||||
memory: string;
|
vendor: string;
|
||||||
gram: string;
|
index: number;
|
||||||
|
core_total: number;
|
||||||
|
core_allocated: number;
|
||||||
|
core_utilization_rate: number;
|
||||||
|
memory_total: number;
|
||||||
|
memory_allocated: number;
|
||||||
|
memory_used: number;
|
||||||
|
temperature: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeItem {
|
export interface Filesystem {
|
||||||
id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
mount_point: string;
|
||||||
|
mount_from: string;
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
free: number;
|
||||||
|
available: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Kernel {
|
||||||
|
name: string;
|
||||||
|
release: string;
|
||||||
|
version: string;
|
||||||
|
architecture: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListItem {
|
||||||
|
name: string;
|
||||||
|
hostname: string;
|
||||||
address: string;
|
address: string;
|
||||||
hostname: string;
|
|
||||||
labels: object;
|
labels: object;
|
||||||
resources: {
|
status: {
|
||||||
capacity: Capacity;
|
cpu: {
|
||||||
allocable: Capacity;
|
total: number;
|
||||||
|
allocated: number;
|
||||||
|
utilization_rate: number;
|
||||||
|
};
|
||||||
|
memory: {
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
allocated: number;
|
||||||
|
};
|
||||||
|
gpu: Gpu[];
|
||||||
|
swap: {
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
};
|
||||||
|
filesystem: Filesystem[];
|
||||||
|
os: {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
kernel: Kernel;
|
||||||
|
uptime: {
|
||||||
|
uptime: number;
|
||||||
|
boot_time: string;
|
||||||
|
};
|
||||||
|
state: string;
|
||||||
};
|
};
|
||||||
state: string;
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
|
||||||
|
export const MODELS_API = '/models';
|
||||||
|
|
||||||
|
export async function queryModelsList(
|
||||||
|
params: Global.Pagination & { query?: string }
|
||||||
|
) {
|
||||||
|
return request<Global.PageResponse<ListItem>>(`${MODELS_API}`, {
|
||||||
|
methos: 'GET',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createModel(params: { data: FormData }) {
|
||||||
|
return request(`${MODELS_API}`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteModel(id: number) {
|
||||||
|
return request(`${MODELS_API}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateModel(params: { id: number; data: FormData }) {
|
||||||
|
return request(`${MODELS_API}/${params.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,34 +1,92 @@
|
|||||||
import ModalFooter from '@/components/modal-footer';
|
import ModalFooter from '@/components/modal-footer';
|
||||||
import FieldWrapper from '@/components/seal-form/field-wrapper';
|
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { Form, Modal, Slider } from 'antd';
|
import { Form, Modal } from 'antd';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOk: () => void;
|
onOk: (values: FormData) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sourceOptions = [
|
const sourceOptions = [
|
||||||
{ label: 'Huggingface', value: 'huggingface' },
|
{ label: 'Huggingface', value: 'huggingface', key: 'huggingface' },
|
||||||
{ label: 'S3', value: 's3' }
|
{ label: 'S3', value: 's3', key: 's3' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const AddModal: React.FC<AddModalProps> = (props) => {
|
const AddModal: React.FC<AddModalProps> = (props) => {
|
||||||
const { title, action, open, onOk, onCancel } = props || {};
|
const { title, action, open, onOk, onCancel } = props || {};
|
||||||
if (!open) {
|
if (!open) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
console.log('modal open', open);
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const modelSource = Form.useWatch('source', form);
|
||||||
|
|
||||||
|
const initFormValue = () => {
|
||||||
|
if (action === PageAction.CREATE && open) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
source: 'huggingface'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initFormValue();
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const renderHuggingfaceFields = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="huggingface_repo_id"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<SealInput.Input label="Huggingface ID" required></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
{/* <Form.Item<FormData>
|
||||||
|
name="huggingface_filename"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label="Huggingface File Name"
|
||||||
|
required
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item> */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderS3Fields = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData> name="s3_address" rules={[{ required: true }]}>
|
||||||
|
<SealInput.Input label="S3 Address" required></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSourceChange = (value: string) => {
|
||||||
|
console.log('source change', value);
|
||||||
|
};
|
||||||
|
const handleSumit = () => {
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
const handleOnFinish = (values: FormData) => {
|
||||||
|
console.log('onFinish', values);
|
||||||
|
onOk(values);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
title={title}
|
||||||
open={open}
|
open={open}
|
||||||
onOk={onOk}
|
onOk={handleSumit}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
closeIcon={false}
|
closeIcon={false}
|
||||||
@@ -36,30 +94,29 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={600}
|
width={600}
|
||||||
styles={{}}
|
styles={{}}
|
||||||
footer={<ModalFooter onOk={onOk} onCancel={onCancel}></ModalFooter>}
|
footer={
|
||||||
|
<ModalFooter onCancel={onCancel} onOk={handleSumit}></ModalFooter>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Form name="addModalForm" form={form} onFinish={onOk}>
|
<Form name="addModalForm" form={form} onFinish={onOk} preserve={false}>
|
||||||
<Form.Item name="name" rules={[{ required: true }]}>
|
<Form.Item<FormData> name="name" rules={[{ required: true }]}>
|
||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
label="Name"
|
label="Name"
|
||||||
required
|
required
|
||||||
description="description info"
|
description="description info"
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="source" rules={[{ required: true }]}>
|
<Form.Item<FormData> name="source" rules={[{ required: true }]}>
|
||||||
<SealSelect label="Model Source" options={sourceOptions}></SealSelect>
|
<SealSelect
|
||||||
</Form.Item>
|
label="Source"
|
||||||
<Form.Item name="id" rules={[{ required: true }]}>
|
options={sourceOptions}
|
||||||
<SealSelect label="Huggingface ID" showSearch></SealSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="tokenPers" rules={[{ required: true }]}>
|
|
||||||
<FieldWrapper
|
|
||||||
label="tokenPers"
|
|
||||||
required
|
required
|
||||||
description="description info"
|
onChange={handleSourceChange}
|
||||||
>
|
></SealSelect>
|
||||||
<Slider defaultValue={30} style={{ width: '100%' }} />
|
</Form.Item>
|
||||||
</FieldWrapper>
|
{modelSource === 's3' ? renderS3Fields() : renderHuggingfaceFields()}
|
||||||
|
<Form.Item<FormData> name="description">
|
||||||
|
<SealInput.TextArea label="Description"></SealInput.TextArea>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export const modelInstanceCols = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Create Time',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Utilization',
|
||||||
|
dataIndex: 'utilization',
|
||||||
|
key: 'utilization'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Host Name',
|
||||||
|
dataIndex: 'hostName',
|
||||||
|
key: 'hostName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Operation',
|
||||||
|
key: 'Operation'
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export interface ListItem {
|
||||||
|
source: string;
|
||||||
|
huggingface_repo_id: string;
|
||||||
|
huggingface_file_name: string;
|
||||||
|
s3Address: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormData {
|
||||||
|
source: string;
|
||||||
|
huggingface_repo_id: string;
|
||||||
|
huggingface_filename: string;
|
||||||
|
s3_address: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
@@ -5,9 +5,7 @@ import useTableRowSelection from '@/hooks/use-table-row-selection';
|
|||||||
import useTableSort from '@/hooks/use-table-sort';
|
import useTableSort from '@/hooks/use-table-sort';
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownOutlined,
|
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
RightOutlined,
|
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
WechatWorkOutlined
|
WechatWorkOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -24,30 +22,14 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
message
|
message
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { StrictMode, useState } from 'react';
|
import dayjs from 'dayjs';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { createModel, deleteModel, queryModelsList } from './apis';
|
||||||
import AddModal from './components/add-modal';
|
import AddModal from './components/add-modal';
|
||||||
|
import { FormData, ListItem } from './config/types';
|
||||||
const { Column } = Table;
|
const { Column } = Table;
|
||||||
|
|
||||||
const dataSource = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
name: 'llama3:latest',
|
|
||||||
progress: 30,
|
|
||||||
transition: true,
|
|
||||||
createTime: '2024-05-22 12:20:10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
name: 'openbmb/MiniCPM-Llama3-V-2_5',
|
|
||||||
createTime: '2024-05-19 13:30:22'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '3',
|
|
||||||
name: 'openbmb/MiniCPM-Llama3-V-2_5',
|
|
||||||
createTime: '2024-05-18 10:28:32'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const Models: React.FC = () => {
|
const Models: React.FC = () => {
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
@@ -62,17 +44,43 @@ const Models: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
||||||
const [title, setTitle] = useState<string>('');
|
const [title, setTitle] = useState<string>('');
|
||||||
|
const [dataSource, setDataSource] = useState<ListItem[]>([]);
|
||||||
const [queryParams, setQueryParams] = useState({
|
const [queryParams, setQueryParams] = useState({
|
||||||
current: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
perPage: 10,
|
||||||
name: ''
|
query: ''
|
||||||
});
|
});
|
||||||
const handleShowSizeChange = (current: number, size: number) => {
|
|
||||||
console.log(current, size);
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
..._.pickBy(queryParams, (val: any) => !!val)
|
||||||
|
};
|
||||||
|
const res = await queryModelsList(params);
|
||||||
|
console.log('res=======', res);
|
||||||
|
setDataSource(res.items);
|
||||||
|
setTotal(res.pagination.total);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleShowSizeChange = (page: number, size: number) => {
|
||||||
|
console.log(page, size);
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
perPage: size
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageChange = (page: number, pageSize: number | undefined) => {
|
const handlePageChange = (page: number, pageSize: number | undefined) => {
|
||||||
console.log(page, pageSize);
|
console.log(page, pageSize);
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
page: page
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||||
@@ -80,9 +88,6 @@ const Models: React.FC = () => {
|
|||||||
setSortOrder(sorter.order);
|
setSortOrder(sorter.order);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
console.log('fetchData');
|
|
||||||
};
|
|
||||||
const handleSearch = (e: any) => {
|
const handleSearch = (e: any) => {
|
||||||
fetchData();
|
fetchData();
|
||||||
};
|
};
|
||||||
@@ -90,7 +95,7 @@ const Models: React.FC = () => {
|
|||||||
const handleNameChange = (e: any) => {
|
const handleNameChange = (e: any) => {
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
name: e.target.value
|
query: e.target.value
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,17 +109,32 @@ const Models: React.FC = () => {
|
|||||||
console.log('click', e);
|
console.log('click', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalOk = () => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
console.log('handleModalOk');
|
console.log('handleModalOk', data);
|
||||||
|
await createModel({ data });
|
||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
|
message.success('successfully!');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalCancel = () => {
|
const handleModalCancel = () => {
|
||||||
console.log('handleModalCancel');
|
console.log('handleModalCancel');
|
||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
};
|
};
|
||||||
|
const handleDelete = async (row: any) => {
|
||||||
const handleDelete = () => {
|
Modal.confirm({
|
||||||
|
title: '',
|
||||||
|
content: 'Are you sure you want to delete the selected models?',
|
||||||
|
async onOk() {
|
||||||
|
await deleteModel(row.id);
|
||||||
|
message.success('successfully!');
|
||||||
|
fetchData();
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleDeleteBatch = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '',
|
title: '',
|
||||||
content: 'Are you sure you want to delete the selected models?',
|
content: 'Are you sure you want to delete the selected models?',
|
||||||
@@ -132,8 +152,15 @@ const Models: React.FC = () => {
|
|||||||
console.log('handleOpenPlayGround', row);
|
console.log('handleOpenPlayGround', row);
|
||||||
navigate('/playground');
|
navigate('/playground');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// request data
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [queryParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<>
|
||||||
<PageContainer
|
<PageContainer
|
||||||
ghost
|
ghost
|
||||||
header={{
|
header={{
|
||||||
@@ -148,6 +175,7 @@ const Models: React.FC = () => {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="按名称查询"
|
placeholder="按名称查询"
|
||||||
style={{ width: 300 }}
|
style={{ width: 300 }}
|
||||||
|
allowClear
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
></Input>
|
></Input>
|
||||||
<Button
|
<Button
|
||||||
@@ -171,7 +199,7 @@ const Models: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
danger
|
danger
|
||||||
onClick={handleDelete}
|
onClick={handleDeleteBatch}
|
||||||
disabled={!rowSelection.selectedRowKeys.length}
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -184,27 +212,17 @@ const Models: React.FC = () => {
|
|||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
pageSize: 10,
|
pageSize: queryParams.perPage,
|
||||||
current: 2,
|
current: queryParams.page,
|
||||||
total: total,
|
total: total,
|
||||||
hideOnSinglePage: true,
|
hideOnSinglePage: true,
|
||||||
onShowSizeChange: handleShowSizeChange,
|
onShowSizeChange: handleShowSizeChange,
|
||||||
onChange: handlePageChange
|
onChange: handlePageChange
|
||||||
}}
|
}}
|
||||||
expandable={{
|
|
||||||
expandIcon: ({ expanded, onExpand, record }) => {
|
|
||||||
return expanded ? (
|
|
||||||
<DownOutlined onClick={(e) => onExpand(record, e)} />
|
|
||||||
) : (
|
|
||||||
<RightOutlined onClick={(e) => onExpand(record, e)} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
expandedRowRender: (record) => <p style={{ margin: 0 }}>list</p>,
|
|
||||||
rowExpandable: (record) => record.name !== 'Not Expandable'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
title="Model Name"
|
title="Model Name"
|
||||||
@@ -227,12 +245,15 @@ const Models: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="Create Time"
|
title="Create Time"
|
||||||
dataIndex="createTime"
|
dataIndex="created_at"
|
||||||
key="createTime"
|
key="createTime"
|
||||||
defaultSortOrder="descend"
|
defaultSortOrder="descend"
|
||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
sorter={true}
|
sorter={true}
|
||||||
|
render={(val, row) => {
|
||||||
|
return dayjs(val).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="Operation"
|
title="Operation"
|
||||||
@@ -253,6 +274,7 @@ const Models: React.FC = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
danger
|
danger
|
||||||
|
onClick={() => handleDelete(record)}
|
||||||
icon={<DeleteOutlined></DeleteOutlined>}
|
icon={<DeleteOutlined></DeleteOutlined>}
|
||||||
></Button>
|
></Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -269,7 +291,7 @@ const Models: React.FC = () => {
|
|||||||
onCancel={handleModalCancel}
|
onCancel={handleModalCancel}
|
||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
></AddModal>
|
></AddModal>
|
||||||
</StrictMode>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
export const USERS_API = '/users';
|
||||||
|
|
||||||
|
export async function queryUsersList(
|
||||||
|
params: Global.Pagination & { query?: string }
|
||||||
|
) {
|
||||||
|
return request<Global.PageResponse<ListItem>>(`${USERS_API}`, {
|
||||||
|
methos: 'GET',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUser(params: { data: FormData }) {
|
||||||
|
return request(`${USERS_API}`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(params: { data: FormData }) {
|
||||||
|
return request(`${USERS_API}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(id: number) {
|
||||||
|
return request(`${USERS_API}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,12 +4,14 @@ import SealSelect from '@/components/seal-form/seal-select';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { SyncOutlined } from '@ant-design/icons';
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
import { Form, Modal } from 'antd';
|
import { Form, Modal } from 'antd';
|
||||||
|
import { UserRolesOptions } from '../config';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOk: () => void;
|
onOk: (values: FormData) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
const AddModal: React.FC<AddModalProps> = ({
|
const AddModal: React.FC<AddModalProps> = ({
|
||||||
@@ -19,6 +21,10 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
onOk,
|
onOk,
|
||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
|
if (!open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const suffix = (
|
const suffix = (
|
||||||
<SyncOutlined
|
<SyncOutlined
|
||||||
@@ -28,11 +34,16 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSumit = () => {
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
title={title}
|
||||||
open={open}
|
open={open}
|
||||||
onOk={onOk}
|
onOk={handleSumit}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
closeIcon={false}
|
closeIcon={false}
|
||||||
@@ -40,19 +51,22 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={600}
|
width={600}
|
||||||
styles={{}}
|
styles={{}}
|
||||||
footer={<ModalFooter onOk={onOk} onCancel={onCancel}></ModalFooter>}
|
footer={
|
||||||
|
<ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Form name="addUserForm" form={form} onFinish={onOk}>
|
<Form name="addUserForm" form={form} onFinish={onOk} preserve={false}>
|
||||||
<Form.Item name="name" rules={[{ required: true }]}>
|
<Form.Item<FormData> name="name" rules={[{ required: true }]}>
|
||||||
<SealInput.Input label="Name"></SealInput.Input>
|
<SealInput.Input label="Name"></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="role" rules={[{ required: true }]}>
|
<Form.Item<FormData> name="full_name" rules={[{ required: true }]}>
|
||||||
<SealSelect label="Role"></SealSelect>
|
<SealInput.Input label="FullName"></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="email" rules={[{ required: true }]}>
|
<Form.Item<FormData> name="is_admin" rules={[{ required: true }]}>
|
||||||
<SealInput.Input label="Email"></SealInput.Input>
|
<SealSelect label="Role" options={UserRolesOptions}></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="password" rules={[{ required: true }]}>
|
|
||||||
|
<Form.Item<FormData> name="password" rules={[{ required: true }]}>
|
||||||
<SealInput.Input label="Password"></SealInput.Input>
|
<SealInput.Input label="Password"></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export const UserRoles = {
|
||||||
|
ADMIN: 'admin',
|
||||||
|
USER: 'user'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserRolesOptions = [
|
||||||
|
{ label: '管理员', value: UserRoles.ADMIN },
|
||||||
|
{ label: '普通用户', value: UserRoles.USER }
|
||||||
|
];
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface ListItem {
|
||||||
|
name: string;
|
||||||
|
is_admin: boolean;
|
||||||
|
full_name: string;
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormData {
|
||||||
|
name: string;
|
||||||
|
is_admin: boolean;
|
||||||
|
full_name: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
+78
-34
@@ -13,46 +13,61 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { Button, Input, Modal, Space, Table, Tooltip, message } from 'antd';
|
import { Button, Input, Modal, Space, Table, Tooltip, message } from 'antd';
|
||||||
import { useState } from 'react';
|
import dayjs from 'dayjs';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { createUser, deleteUser, queryUsersList } from './apis';
|
||||||
import AddModal from './components/add-modal';
|
import AddModal from './components/add-modal';
|
||||||
|
import { FormData, ListItem } from './config/types';
|
||||||
const { Column } = Table;
|
const { Column } = Table;
|
||||||
|
|
||||||
const dataSource = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
name: 'sam',
|
|
||||||
role: 'admin',
|
|
||||||
createTime: '2021-09-01 12:00:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
name: 'wang',
|
|
||||||
role: 'user',
|
|
||||||
createTime: '2021-09-01 12:00:00'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const Models: React.FC = () => {
|
const Models: React.FC = () => {
|
||||||
const rowSelection = useTableRowSelection();
|
const rowSelection = useTableRowSelection();
|
||||||
const { sortOrder, setSortOrder } = useTableSort({
|
const { sortOrder, setSortOrder } = useTableSort({
|
||||||
defaultSortOrder: 'descend'
|
defaultSortOrder: 'descend'
|
||||||
});
|
});
|
||||||
const [total, setTotal] = useState(100);
|
const [total, setTotal] = useState(0);
|
||||||
const [openAddModal, setOpenAddModal] = useState(false);
|
const [openAddModal, setOpenAddModal] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [dataSource, setDataSource] = useState<ListItem[]>([]);
|
||||||
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
||||||
const [title, setTitle] = useState<string>('');
|
const [title, setTitle] = useState<string>('');
|
||||||
const [queryParams, setQueryParams] = useState({
|
const [queryParams, setQueryParams] = useState({
|
||||||
current: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
perPage: 10,
|
||||||
name: ''
|
query: ''
|
||||||
});
|
});
|
||||||
const handleShowSizeChange = (current: number, size: number) => {
|
|
||||||
console.log(current, size);
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
..._.pickBy(queryParams, (val: any) => !!val)
|
||||||
|
};
|
||||||
|
const res = await queryUsersList(params);
|
||||||
|
console.log('res=======', res);
|
||||||
|
setDataSource(res.items);
|
||||||
|
setTotal(res.pagination.total);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShowSizeChange = (page: number, size: number) => {
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
perPage: size
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageChange = (page: number, pageSize: number | undefined) => {
|
const handlePageChange = (page: number, pageSize: number | undefined) => {
|
||||||
console.log(page, pageSize);
|
console.log(page, pageSize);
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
page: page
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||||
@@ -60,9 +75,6 @@ const Models: React.FC = () => {
|
|||||||
setSortOrder(sorter.order);
|
setSortOrder(sorter.order);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
console.log('fetchData');
|
|
||||||
};
|
|
||||||
const handleSearch = (e: any) => {
|
const handleSearch = (e: any) => {
|
||||||
fetchData();
|
fetchData();
|
||||||
};
|
};
|
||||||
@@ -70,7 +82,7 @@ const Models: React.FC = () => {
|
|||||||
const handleNameChange = (e: any) => {
|
const handleNameChange = (e: any) => {
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
name: e.target.value
|
query: e.target.value
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,9 +96,15 @@ const Models: React.FC = () => {
|
|||||||
console.log('click', e);
|
console.log('click', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalOk = () => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
console.log('handleModalOk');
|
console.log('handleModalOk');
|
||||||
|
const params = {
|
||||||
|
...data,
|
||||||
|
is_admin: data.is_admin === 'admin'
|
||||||
|
};
|
||||||
|
await createUser({ data: params });
|
||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
|
message.success('successfully!');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalCancel = () => {
|
const handleModalCancel = () => {
|
||||||
@@ -94,7 +112,23 @@ const Models: React.FC = () => {
|
|||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = (row: ListItem) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '',
|
||||||
|
content: 'Are you sure you want to delete the selected users?',
|
||||||
|
async onOk() {
|
||||||
|
console.log('OK');
|
||||||
|
await deleteUser(row.id);
|
||||||
|
message.success('successfully!');
|
||||||
|
fetchData();
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteBatch = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '',
|
title: '',
|
||||||
content: 'Are you sure you want to delete the selected users?',
|
content: 'Are you sure you want to delete the selected users?',
|
||||||
@@ -113,6 +147,11 @@ const Models: React.FC = () => {
|
|||||||
setAction(PageAction.EDIT);
|
setAction(PageAction.EDIT);
|
||||||
setTitle('Edit User');
|
setTitle('Edit User');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [queryParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageContainer
|
<PageContainer
|
||||||
@@ -151,7 +190,7 @@ const Models: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
danger
|
danger
|
||||||
onClick={handleDelete}
|
onClick={handleDeleteBatch}
|
||||||
disabled={!rowSelection.selectedRowKeys.length}
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -163,11 +202,12 @@ const Models: React.FC = () => {
|
|||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
pageSize: 10,
|
pageSize: queryParams.perPage,
|
||||||
current: 2,
|
current: queryParams.page,
|
||||||
total: total,
|
total: total,
|
||||||
hideOnSinglePage: true,
|
hideOnSinglePage: true,
|
||||||
onShowSizeChange: handleShowSizeChange,
|
onShowSizeChange: handleShowSizeChange,
|
||||||
@@ -177,20 +217,23 @@ const Models: React.FC = () => {
|
|||||||
<Column title="Name" dataIndex="name" key="name" width={400} />
|
<Column title="Name" dataIndex="name" key="name" width={400} />
|
||||||
<Column
|
<Column
|
||||||
title="Create Time"
|
title="Create Time"
|
||||||
dataIndex="createTime"
|
dataIndex="created_at"
|
||||||
key="createTime"
|
key="createTime"
|
||||||
defaultSortOrder="descend"
|
defaultSortOrder="descend"
|
||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
sorter={true}
|
sorter={true}
|
||||||
|
render={(text, record) => {
|
||||||
|
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="Role"
|
title="Role"
|
||||||
dataIndex="role"
|
dataIndex="role"
|
||||||
key="role"
|
key="role"
|
||||||
width={400}
|
width={400}
|
||||||
render={(text, record) => {
|
render={(text, record: ListItem) => {
|
||||||
return text === 'admin' ? (
|
return record.is_admin ? (
|
||||||
<UserSwitchOutlined className="size-16" />
|
<UserSwitchOutlined className="size-16" />
|
||||||
) : (
|
) : (
|
||||||
<UserOutlined className="size-16" />
|
<UserOutlined className="size-16" />
|
||||||
@@ -216,6 +259,7 @@ const Models: React.FC = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
danger
|
danger
|
||||||
|
onClick={() => handleDelete(record)}
|
||||||
icon={<DeleteOutlined></DeleteOutlined>}
|
icon={<DeleteOutlined></DeleteOutlined>}
|
||||||
></Button>
|
></Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { RequestConfig } from '@umijs/max';
|
||||||
|
import { message } from 'antd';
|
||||||
|
|
||||||
|
export const requestConfig: RequestConfig = {
|
||||||
|
errorConfig: {
|
||||||
|
errorThrower: (res: any) => {
|
||||||
|
console.log('errorThrower+++++++++++++++', res);
|
||||||
|
},
|
||||||
|
errorHandler: (error: any, opts: any) => {
|
||||||
|
const { message: errorMessage, response } = error;
|
||||||
|
const errMsg = response?.data?.message || errorMessage;
|
||||||
|
message.error(errMsg);
|
||||||
|
console.log('errorHandler+++++++++++++++', error, opts);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestInterceptors: [
|
||||||
|
(url, options) => {
|
||||||
|
console.log('requestInterceptors+++++++++++++++', url, options);
|
||||||
|
return { url, options };
|
||||||
|
}
|
||||||
|
],
|
||||||
|
responseInterceptors: [
|
||||||
|
(response) => {
|
||||||
|
console.log('responseInterceptors+++++++++++++++', response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user