diff --git a/config/config.ts b/config/config.ts index 874b5583..fa3f54bb 100644 --- a/config/config.ts +++ b/config/config.ts @@ -1,7 +1,14 @@ import { defineConfig } from '@umijs/max'; +import proxy from './proxy'; import routes from './routes'; +const { REACT_APP_ENV = 'dev' } = process.env; + +console.log('env+++++++++++++++', process.env.NODE_ENV, REACT_APP_ENV); export default defineConfig({ + proxy: { + ...proxy() + }, clickToComponent: {}, antd: { style: 'less', diff --git a/config/proxy.ts b/config/proxy.ts new file mode 100644 index 00000000..a887ff36 --- /dev/null +++ b/config/proxy.ts @@ -0,0 +1,23 @@ +const proxyTableList = ['cli', 'v1']; + +// @ts-ingore +export default function createProxyTable(target?: string) { + const proxyTable = proxyTableList.reduce( + (obj: Record, 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; +} diff --git a/package.json b/package.json index a8cfb49e..099c8c69 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "antd-style": "^3.6.2", "classnames": "^2.5.1", "crypto-js": "^4.2.0", + "dayjs": "^1.11.11", "lodash": "^4.17.21", "numeral": "^2.0.6" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7955021f..9cc9e05e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: crypto-js: specifier: ^4.2.0 version: 4.2.0 + dayjs: + specifier: ^1.11.11 + version: 1.11.11 lodash: specifier: ^4.17.21 version: 4.17.21 diff --git a/src/app.tsx b/src/app.tsx index 6f1c7cbd..a7cb0690 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,3 +1,5 @@ +import { RequestConfig } from '@umijs/max'; +import { requestConfig } from './request-config'; // 运行时配置 // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 @@ -8,3 +10,8 @@ export async function getInitialState(): Promise<{ name: string }> { name: 'admin' }; } + +export const request: RequestConfig = { + baseURL: ' /v1', + ...requestConfig +}; diff --git a/src/components/modal-footer/index.tsx b/src/components/modal-footer/index.tsx index 050c2813..921dec24 100644 --- a/src/components/modal-footer/index.tsx +++ b/src/components/modal-footer/index.tsx @@ -1,23 +1,32 @@ import { Button, Space } from 'antd'; type ModalFooterProps = { - onOk: () => void; + onOk?: () => void; onCancel: () => void; cancelText?: string; okText?: string; + htmlType?: 'button' | 'submit'; + form?: any; }; const ModalFooter: React.FC = ({ onOk, onCancel, cancelText, - okText + okText, + htmlType = 'button', + form }) => { return ( - diff --git a/src/components/status-tag/index.tsx b/src/components/status-tag/index.tsx index fa754f90..ddf2edbd 100644 --- a/src/components/status-tag/index.tsx +++ b/src/components/status-tag/index.tsx @@ -4,10 +4,11 @@ import { useEffect, useState } from 'react'; import styles from './index.less'; export const StatusMaps = { - running: 'blue', + transitioning: 'blue', error: 'red', warning: 'orange', - success: 'success' + success: 'success', + inactive: 'inactive' }; type StatusTagProps = { diff --git a/src/config/global.d.ts b/src/config/global.d.ts new file mode 100644 index 00000000..07dfdd81 --- /dev/null +++ b/src/config/global.d.ts @@ -0,0 +1,16 @@ +declare namespace Global { + interface Pagination { + page: number; + perPage: number; + watch?: boolean; + } + interface PageResponse { + items: T[]; + pagination: { + total: number; + totalPage: number; + page: number; + perPage: number; + }; + } +} diff --git a/src/pages/Resources/apis/index.ts b/src/pages/Resources/apis/index.ts new file mode 100644 index 00000000..c24f2eda --- /dev/null +++ b/src/pages/Resources/apis/index.ts @@ -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>(`${NODES_API}`, { + methos: 'GET', + params + }); +} diff --git a/src/pages/Resources/components/gpus.tsx b/src/pages/Resources/components/gpus.tsx index 7a52b761..a1524985 100644 --- a/src/pages/Resources/components/gpus.tsx +++ b/src/pages/Resources/components/gpus.tsx @@ -5,11 +5,11 @@ import useTableSort from '@/hooks/use-table-sort'; import { SyncOutlined } from '@ant-design/icons'; import { Button, Input, Space, Table } from 'antd'; import { useState } from 'react'; -import { NodeItem } from '../config/types'; +import { Gpu } from '../config/types'; import RenderProgress from './render-progress'; const { Column } = Table; -const dataSource: NodeItem[] = [ +const dataSource: Gpu[] = [ { id: 1, name: 'bj-web-service-1', @@ -161,7 +161,6 @@ const Models: React.FC = () => { > { /> - + @@ -201,10 +204,8 @@ const Models: React.FC = () => { title="VRAM" dataIndex="GRAM" key="VRAM" - render={(text, record: NodeItem) => { - return ( - - ); + render={(text, record: Gpu) => { + return ; }} /> { const rowSelection = useTableRowSelection(); const { sortOrder, setSortOrder } = useTableSort({ defaultSortOrder: 'descend' }); - const [total, setTotal] = useState(10); + const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState([]); const [queryParams, setQueryParams] = useState({ - current: 1, - pageSize: 10, - name: '' + page: 1, + perPage: 10, + 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) => { console.log(current, size); }; - const handlePageChange = (page: number, pageSize: number | undefined) => { - console.log(page, pageSize); + const handlePageChange = (page: number, perPage: number | undefined) => { + console.log(page, perPage); + setQueryParams({ + ...queryParams, + page: page + }); }; const handleTableChange = (pagination: any, filters: any, sorter: any) => { setSortOrder(sorter.order); }; - const fetchData = async () => { - console.log('fetchData'); - }; const handleSearch = (e: any) => { fetchData(); }; @@ -134,10 +64,20 @@ const Models: React.FC = () => { const handleNameChange = (e: any) => { setQueryParams({ ...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 ( <> { >
{ title="State" dataIndex="state" key="state" - render={(text, record) => { + render={(text, record: ListItem) => { return ( ); @@ -196,9 +136,11 @@ const Models: React.FC = () => { title="CPU" dataIndex="CPU" key="CPU" - render={(text, record: NodeItem) => { + render={(text, record: ListItem) => { return ( - + ); }} /> @@ -206,11 +148,13 @@ const Models: React.FC = () => { title="Memory" dataIndex="memory" key="Memory" - render={(text, record: NodeItem) => { + render={(text, record: ListItem) => { return ( ); }} @@ -219,20 +163,16 @@ const Models: React.FC = () => { title="GPU" dataIndex="GPU" key="GPU" - render={(text, record: NodeItem) => { - return ( - - ); + render={(text, record: ListItem) => { + return ; }} /> { - return ( - - ); + render={(text, record: ListItem) => { + return ; }} /> { - 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 RenderProgress = memo((props: { percent: number }) => { + const { percent } = props; + // 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]); + // 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 ; + // if (!value1 || !value2) { + // return ; + // } + // 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; - 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} - /> - ); - } -); + 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/Resources/config/types.ts b/src/pages/Resources/config/types.ts index f81761cd..29ee0d6d 100644 --- a/src/pages/Resources/config/types.ts +++ b/src/pages/Resources/config/types.ts @@ -1,19 +1,68 @@ -export interface Capacity { - cpu: number; - gpu: number; - memory: string; - gram: string; +export interface Gpu { + uuid: string; + name: string; + vendor: 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 { - id: number; +export interface Filesystem { 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; - hostname: string; labels: object; - resources: { - capacity: Capacity; - allocable: Capacity; + status: { + cpu: { + 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; } diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts new file mode 100644 index 00000000..25ac94d4 --- /dev/null +++ b/src/pages/llmodels/apis/index.ts @@ -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>(`${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 + }); +} diff --git a/src/pages/llmodels/components/add-modal.tsx b/src/pages/llmodels/components/add-modal.tsx index 44e6e0d0..9dcc9c1e 100644 --- a/src/pages/llmodels/components/add-modal.tsx +++ b/src/pages/llmodels/components/add-modal.tsx @@ -1,34 +1,92 @@ 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 { PageAction } from '@/config'; 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 = { title: string; action: PageActionType; open: boolean; - onOk: () => void; + onOk: (values: FormData) => void; onCancel: () => void; }; const sourceOptions = [ - { label: 'Huggingface', value: 'huggingface' }, - { label: 'S3', value: 's3' } + { label: 'Huggingface', value: 'huggingface', key: 'huggingface' }, + { label: 'S3', value: 's3', key: 's3' } ]; + const AddModal: React.FC = (props) => { const { title, action, open, onOk, onCancel } = props || {}; if (!open) { return null; } - console.log('modal open', open); 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 ( + <> + + name="huggingface_repo_id" + rules={[{ required: true }]} + > + + + {/* + name="huggingface_filename" + rules={[{ required: true }]} + > + + */} + + ); + }; + + const renderS3Fields = () => { + return ( + <> + name="s3_address" rules={[{ required: true }]}> + + + + ); + }; + + const handleSourceChange = (value: string) => { + console.log('source change', value); + }; + const handleSumit = () => { + form.submit(); + }; + const handleOnFinish = (values: FormData) => { + console.log('onFinish', values); + onOk(values); + }; return ( = (props) => { keyboard={false} width={600} styles={{}} - footer={} + footer={ + + } > -
- + + name="name" rules={[{ required: true }]}> - - - - - - - - name="source" rules={[{ required: true }]}> + - - + onChange={handleSourceChange} + > + + {modelSource === 's3' ? renderS3Fields() : renderHuggingfaceFields()} + name="description"> +
diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts new file mode 100644 index 00000000..d2e89f59 --- /dev/null +++ b/src/pages/llmodels/config/index.ts @@ -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' + } +]; diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts new file mode 100644 index 00000000..a8fb243c --- /dev/null +++ b/src/pages/llmodels/config/types.ts @@ -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; +} diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index c302e4c5..fd8cc3f8 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -5,9 +5,7 @@ import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableSort from '@/hooks/use-table-sort'; import { DeleteOutlined, - DownOutlined, PlusOutlined, - RightOutlined, SyncOutlined, WechatWorkOutlined } from '@ant-design/icons'; @@ -24,30 +22,14 @@ import { Tooltip, message } 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 { FormData, ListItem } from './config/types'; 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 { modal } = App.useApp(); const access = useAccess(); @@ -62,17 +44,43 @@ const Models: React.FC = () => { const [loading, setLoading] = useState(false); const [action, setAction] = useState(PageAction.CREATE); const [title, setTitle] = useState(''); + const [dataSource, setDataSource] = useState([]); const [queryParams, setQueryParams] = useState({ - current: 1, - pageSize: 10, - name: '' + page: 1, + perPage: 10, + 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) => { console.log(page, pageSize); + setQueryParams({ + ...queryParams, + page: page + }); }; const handleTableChange = (pagination: any, filters: any, sorter: any) => { @@ -80,9 +88,6 @@ const Models: React.FC = () => { setSortOrder(sorter.order); }; - const fetchData = async () => { - console.log('fetchData'); - }; const handleSearch = (e: any) => { fetchData(); }; @@ -90,7 +95,7 @@ const Models: React.FC = () => { const handleNameChange = (e: any) => { setQueryParams({ ...queryParams, - name: e.target.value + query: e.target.value }); }; @@ -104,17 +109,32 @@ const Models: React.FC = () => { console.log('click', e); }; - const handleModalOk = () => { - console.log('handleModalOk'); + const handleModalOk = async (data: FormData) => { + console.log('handleModalOk', data); + await createModel({ data }); setOpenAddModal(false); + message.success('successfully!'); }; const handleModalCancel = () => { console.log('handleModalCancel'); setOpenAddModal(false); }; - - const handleDelete = () => { + const handleDelete = async (row: any) => { + 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({ title: '', content: 'Are you sure you want to delete the selected models?', @@ -132,8 +152,15 @@ const Models: React.FC = () => { console.log('handleOpenPlayGround', row); navigate('/playground'); }; + + // request data + + useEffect(() => { + fetchData(); + }, [queryParams]); + return ( - + <> { @@ -269,7 +291,7 @@ const Models: React.FC = () => { onCancel={handleModalCancel} onOk={handleModalOk} > - + ); }; diff --git a/src/pages/users/apis/index.ts b/src/pages/users/apis/index.ts new file mode 100644 index 00000000..cf4b55cf --- /dev/null +++ b/src/pages/users/apis/index.ts @@ -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>(`${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' + }); +} diff --git a/src/pages/users/components/add-modal.tsx b/src/pages/users/components/add-modal.tsx index 3b312eba..8ffe4d3c 100644 --- a/src/pages/users/components/add-modal.tsx +++ b/src/pages/users/components/add-modal.tsx @@ -4,12 +4,14 @@ import SealSelect from '@/components/seal-form/seal-select'; import { PageActionType } from '@/config/types'; import { SyncOutlined } from '@ant-design/icons'; import { Form, Modal } from 'antd'; +import { UserRolesOptions } from '../config'; +import { FormData } from '../config/types'; type AddModalProps = { title: string; action: PageActionType; open: boolean; - onOk: () => void; + onOk: (values: FormData) => void; onCancel: () => void; }; const AddModal: React.FC = ({ @@ -19,6 +21,10 @@ const AddModal: React.FC = ({ onOk, onCancel }) => { + if (!open) { + return null; + } + const [form] = Form.useForm(); const suffix = ( = ({ }} /> ); + + const handleSumit = () => { + form.submit(); + }; + return ( = ({ keyboard={false} width={600} styles={{}} - footer={} + footer={ + + } > -
- + + name="name" rules={[{ required: true }]}> - - + name="full_name" rules={[{ required: true }]}> + - - + name="is_admin" rules={[{ required: true }]}> + - + + name="password" rules={[{ required: true }]}> diff --git a/src/pages/users/config/index.ts b/src/pages/users/config/index.ts new file mode 100644 index 00000000..6155181b --- /dev/null +++ b/src/pages/users/config/index.ts @@ -0,0 +1,9 @@ +export const UserRoles = { + ADMIN: 'admin', + USER: 'user' +}; + +export const UserRolesOptions = [ + { label: '管理员', value: UserRoles.ADMIN }, + { label: '普通用户', value: UserRoles.USER } +]; diff --git a/src/pages/users/config/types.ts b/src/pages/users/config/types.ts new file mode 100644 index 00000000..a601e6a5 --- /dev/null +++ b/src/pages/users/config/types.ts @@ -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; +} diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 495e4339..25e74163 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -13,46 +13,61 @@ import { } 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 dayjs from 'dayjs'; +import _ from 'lodash'; +import { useEffect, useState } from 'react'; +import { createUser, deleteUser, queryUsersList } from './apis'; import AddModal from './components/add-modal'; +import { FormData, ListItem } from './config/types'; 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 rowSelection = useTableRowSelection(); const { sortOrder, setSortOrder } = useTableSort({ defaultSortOrder: 'descend' }); - const [total, setTotal] = useState(100); + const [total, setTotal] = useState(0); const [openAddModal, setOpenAddModal] = useState(false); const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState([]); const [action, setAction] = useState(PageAction.CREATE); const [title, setTitle] = useState(''); const [queryParams, setQueryParams] = useState({ - current: 1, - pageSize: 10, - name: '' + page: 1, + perPage: 10, + 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) => { console.log(page, pageSize); + setQueryParams({ + ...queryParams, + page: page + }); }; const handleTableChange = (pagination: any, filters: any, sorter: any) => { @@ -60,9 +75,6 @@ const Models: React.FC = () => { setSortOrder(sorter.order); }; - const fetchData = async () => { - console.log('fetchData'); - }; const handleSearch = (e: any) => { fetchData(); }; @@ -70,7 +82,7 @@ const Models: React.FC = () => { const handleNameChange = (e: any) => { setQueryParams({ ...queryParams, - name: e.target.value + query: e.target.value }); }; @@ -84,9 +96,15 @@ const Models: React.FC = () => { console.log('click', e); }; - const handleModalOk = () => { + const handleModalOk = async (data: FormData) => { console.log('handleModalOk'); + const params = { + ...data, + is_admin: data.is_admin === 'admin' + }; + await createUser({ data: params }); setOpenAddModal(false); + message.success('successfully!'); }; const handleModalCancel = () => { @@ -94,7 +112,23 @@ const Models: React.FC = () => { 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({ title: '', content: 'Are you sure you want to delete the selected users?', @@ -113,6 +147,11 @@ const Models: React.FC = () => { setAction(PageAction.EDIT); setTitle('Edit User'); }; + + useEffect(() => { + fetchData(); + }, [queryParams]); + return ( <> { diff --git a/src/request-config.ts b/src/request-config.ts new file mode 100644 index 00000000..4118d756 --- /dev/null +++ b/src/request-config.ts @@ -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; + } + ] +};