From 96e83878d1f7d0fe7ba9c7666ed78b9bb8e40c68 Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 20 May 2024 18:13:05 +0800 Subject: [PATCH] feat: nodes list --- package.json | 4 +- pnpm-lock.yaml | 10 + src/app.ts | 2 +- src/components/status-tag/index.less | 9 + src/components/status-tag/index.tsx | 44 +++++ src/config/index.ts | 26 ++- src/config/types.ts | 7 + src/global.less | 2 +- src/layouts/index.tsx | 40 ++-- src/layouts/rightRender.tsx | 16 +- src/pages/nodes/config/types.ts | 19 ++ src/pages/nodes/index.tsx | 281 ++++++++++++++++++++++++++- typings.d.ts | 1 + 13 files changed, 415 insertions(+), 46 deletions(-) create mode 100644 src/components/status-tag/index.less create mode 100644 src/components/status-tag/index.tsx create mode 100644 src/pages/nodes/config/types.ts diff --git a/package.json b/package.json index 55732449..e569079a 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "dependencies": { "@ant-design/icons": "^5.3.7", "@ant-design/pro-components": "^2.7.1", + "@types/lodash": "^4.17.4", "@umijs/max": "^4.2.1", "antd": "^5.17.0", "antd-style": "^3.6.2", - "classnames": "^2.5.1" + "classnames": "^2.5.1", + "lodash": "^4.17.21" }, "devDependencies": { "@types/react": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6658b78..708710d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@ant-design/pro-components': specifier: ^2.7.1 version: 2.7.1(antd@5.17.0)(rc-field-form@1.44.0)(react-dom@18.3.1)(react@18.3.1) + '@types/lodash': + specifier: ^4.17.4 + version: 4.17.4 '@umijs/max': specifier: ^4.2.1 version: 4.2.1(@babel/core@7.24.5)(@types/react-dom@18.3.0)(@types/react@18.3.1)(dva@2.5.0-beta.2)(prettier@3.2.5)(rc-field-form@1.44.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(webpack@5.91.0) @@ -23,6 +26,9 @@ dependencies: classnames: specifier: ^2.5.1 version: 2.5.1 + lodash: + specifier: ^4.17.21 + version: 4.17.21 devDependencies: '@types/react': @@ -2611,6 +2617,10 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: false + /@types/lodash@4.17.4: + resolution: {integrity: sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==} + dev: false + /@types/minimist@1.2.5: resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} dev: false diff --git a/src/app.ts b/src/app.ts index 8b7b8328..fe803792 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,7 +5,7 @@ // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate export async function getInitialState(): Promise<{ name: string }> { - return { name: 'admin' }; + return { name: 'admin', logout: true }; } // export const layout: RunTimeLayoutConfig = () => { diff --git a/src/components/status-tag/index.less b/src/components/status-tag/index.less new file mode 100644 index 00000000..cd95a603 --- /dev/null +++ b/src/components/status-tag/index.less @@ -0,0 +1,9 @@ +.status-tag { + display: flex; + justify-content: center; + align-items: center; + padding: 2px 10px; + border-radius: 20px; + width: fit-content; + font-size: var(--font-size-base); +} diff --git a/src/components/status-tag/index.tsx b/src/components/status-tag/index.tsx new file mode 100644 index 00000000..fa754f90 --- /dev/null +++ b/src/components/status-tag/index.tsx @@ -0,0 +1,44 @@ +import { StatusColorMap } from '@/config'; +import { StatusType } from '@/config/types'; +import { useEffect, useState } from 'react'; +import styles from './index.less'; + +export const StatusMaps = { + running: 'blue', + error: 'red', + warning: 'orange', + success: 'success' +}; + +type StatusTagProps = { + statusValue: { + status: StatusType; + text: string; + }; +}; + +const StatusTag: React.FC = ({ statusValue }) => { + const { text, status } = statusValue; + const [statusColor, setStatusColor] = useState<{ text: string; bg: string }>({ + text: '', + bg: '' + }); + + useEffect(() => { + setStatusColor(StatusColorMap[status]); + }, [status]); + + return ( + + {text} + + ); +}; + +export default StatusTag; diff --git a/src/config/index.ts b/src/config/index.ts index 7815d469..b3a2fb79 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,7 +1,31 @@ -import { PageActionType } from './types'; +import { PageActionType, StatusType } from './types'; export const PageAction: Record = { CREATE: 'create', UPDATE: 'update', VIEW: 'view' }; + +export const StatusColorMap: Record = + { + error: { + text: `var(--ant-red-6)`, + bg: `var(--ant-red-1)` + }, + warning: { + text: `var(--ant-orange-6)`, + bg: `var(--ant-orange-1)` + }, + transitioning: { + text: `var(--ant-blue-6)`, + bg: `var(--ant-blue-1)` + }, + success: { + text: `var(--ant-color-primary)`, + bg: `var(--ant-green-1)` + }, + inactive: { + text: `var(--ant-color-border)`, + bg: `var(--ant-color-fill)` + } + }; diff --git a/src/config/types.ts b/src/config/types.ts index 4960c262..8d600a9b 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -7,3 +7,10 @@ export interface DropDownItem { } export type PageActionType = 'create' | 'update' | 'view'; + +export type StatusType = + | 'error' + | 'warning' + | 'transitioning' + | 'success' + | 'inactive'; diff --git a/src/global.less b/src/global.less index d54ad3c2..cba86e7d 100644 --- a/src/global.less +++ b/src/global.less @@ -9,7 +9,7 @@ html { --color-white-1: #fff; --font-weight-normal: 500; --font-weight-bold: 700; - --color-text-1: #1d2129; + --color-text-1: var(--ant-color-text); --color-bg-light-1: #f0fff6; --font-size-base: 12px; --ant-color-fill-secondary: rgba(0, 0, 0, 0.06); diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 9f43f6f3..8c73c1e7 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -12,19 +12,19 @@ import { useAppData, useLocation, useNavigate, - type IRoute, + type IRoute } from '@umijs/max'; import { useMemo } from 'react'; import Exception from './Exception'; import './Layout.css'; import Logo from './Logo'; import { getRightRenderContent } from './rightRender'; -import { renderMenuIcon,patchRoutes } from './runtime'; +import { patchRoutes } from './runtime'; // 过滤出需要显示的路由, 这里的filterFn 指 不希望显示的层级 const filterRoutes = ( routes: IRoute[], - filterFn: (route: IRoute) => boolean, + filterFn: (route: IRoute) => boolean ) => { if (routes.length === 0) { return []; @@ -78,34 +78,33 @@ export default (props: any) => { const navigate = useNavigate(); const { clientRoutes, pluginManager } = useAppData(); - console.log('pluginManager===========', pluginManager); - const initialInfo = (useModel && useModel('@@initialState')) || { initialState: undefined, loading: false, - setInitialState: null, + setInitialState: null }; const { initialState, loading, setInitialState } = initialInfo; const userConfig = { title: '', - layout: 'mix', + layout: 'mix' }; const formatMessage = undefined; const runtimeConfig = pluginManager.applyPlugins({ key: 'layout', type: 'modify', + logout: true, initialValue: { ...initialInfo, notFound:
not found
- }, + } }); console.log( 'clientRoute===========', clientRoutes, runtimeConfig, - initialInfo, + initialInfo ); // 现在的 layout 及 wrapper 实现是通过父路由的形式实现的, 会导致路由数据多了冗余层级, proLayout 消费时, 无法正确展示菜单, 这里对冗余数据进行过滤操作 @@ -116,18 +115,17 @@ export default (props: any) => { (!!route.isLayout && route.id !== '@@/global-layout') || !!route.isWrapper ); - }, + } ); - console.log('clientRoutes===========', clientRoutes, newRoutes) + console.log('clientRoutes===========', clientRoutes, newRoutes); const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes)); patchRoutes({ routes: route?.children || [] }); - const matchedRoute = useMemo( () => matchRoutes(route?.children || [], location.pathname)?.pop?.()?.route, - [location.pathname], + [location.pathname] ); -console.log('route===========', route) + console.log('route===========', route); return ( { - console.log('meurender=========',{ defaultDom}) + console.log('meurender=========', { defaultDom }); if (menuItemProps.isUrl || menuItemProps.children) { return defaultDom; } @@ -158,15 +156,11 @@ console.log('route===========', route) to={menuItemProps.path.replace('/*', '')} target={menuItemProps.target} > - {defaultDom} + {defaultDom} ); } - return ( - <> - {defaultDom} - - ); + return <>{defaultDom}; }} itemRender={(route, _, routes) => { const { breadcrumbName, title, path } = route; @@ -190,7 +184,7 @@ console.log('route===========', route) runtimeConfig, loading, initialState, - setInitialState, + setInitialState }); if (runtimeConfig.rightContentRender) { return runtimeConfig.rightContentRender(layoutProps, dom, { @@ -199,7 +193,7 @@ console.log('route===========', route) runtimeConfig, loading, initialState, - setInitialState, + setInitialState }); } return dom; diff --git a/src/layouts/rightRender.tsx b/src/layouts/rightRender.tsx index 8a61875d..ad05aad8 100644 --- a/src/layouts/rightRender.tsx +++ b/src/layouts/rightRender.tsx @@ -1,6 +1,7 @@ // @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'; @@ -14,7 +15,7 @@ export function getRightRenderContent(opts: { return opts.runtimeConfig.rightRender( opts.initialState, opts.setInitialState, - opts.runtimeConfig, + opts.runtimeConfig ); } @@ -32,10 +33,7 @@ export function getRightRenderContent(opts: { ) : null} @@ -68,9 +66,9 @@ export function getRightRenderContent(opts: { ), onClick: () => { opts?.runtimeConfig?.logout?.(opts.initialState); - }, - }, - ], + } + } + ] }; // antd@5 和 4.24 之后推荐使用 menu,性能更好 let dropdownProps; @@ -86,7 +84,7 @@ export function getRightRenderContent(opts: { ))} - ), + ) }; } else { // 需要 antd 4.20.0 以上版本 diff --git a/src/pages/nodes/config/types.ts b/src/pages/nodes/config/types.ts new file mode 100644 index 00000000..f81761cd --- /dev/null +++ b/src/pages/nodes/config/types.ts @@ -0,0 +1,19 @@ +export interface Capacity { + cpu: number; + gpu: number; + memory: string; + gram: string; +} + +export interface NodeItem { + id: number; + name: string; + address: string; + hostname: string; + labels: object; + resources: { + capacity: Capacity; + allocable: Capacity; + }; + state: string; +} diff --git a/src/pages/nodes/index.tsx b/src/pages/nodes/index.tsx index 58691385..c8c5a545 100644 --- a/src/pages/nodes/index.tsx +++ b/src/pages/nodes/index.tsx @@ -1,16 +1,277 @@ +import PageTools from '@/components/page-tools'; +import StatusTag from '@/components/status-tag'; +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 { 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', + 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: 'ALIVE' + }, + { + id: 1, + name: '192.168.1.2', + address: '192.168.1.5', + hostname: 'node-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: 'ALIVE' + } +]; + +const Models: React.FC = () => { + const rowSelection = useTableRowSelection(); + const { sortOrder, setSortOrder } = useTableSort({ + defaultSortOrder: 'descend' + }); + const [total, setTotal] = useState(100); + const [loading, setLoading] = useState(false); + 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) => { + 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 handleClickMenu = (e: any) => { + console.log('click', e); + }; + + 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} + /> + ); + } + ); -const Dashboard: React.FC = () => { return ( - -
Nodes
-
+ <> + + + + + + } + > + + + { + return ( + + ); + }} + /> + + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ( + + ); + }} + /> + { + return ( + + + + ); + }} + /> +
+
+ ); }; -export default Dashboard; \ No newline at end of file +export default Models; diff --git a/typings.d.ts b/typings.d.ts index 40f11d06..c38e8968 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -14,5 +14,6 @@ declare module 'omit.js'; declare module 'numeral'; declare module 'mockjs'; declare module 'react-fittext'; +declare module 'lodash'; declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;