feat(web): 优化页面跳转和菜单展现

This commit is contained in:
v-zhangjc9
2025-05-12 15:59:46 +08:00
parent 1e7b195f9f
commit b0603d10bc
19 changed files with 336 additions and 209 deletions

View File

@@ -11,7 +11,6 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^6.0.0", "@ant-design/icons": "^6.0.0",
"@ant-design/pro-components": "^2.8.7", "@ant-design/pro-components": "^2.8.7",
"@ant-design/pro-layout": "^7.22.4",
"@ant-design/x": "^1.2.0", "@ant-design/x": "^1.2.0",
"@fortawesome/fontawesome-free": "^6.7.2", "@fortawesome/fontawesome-free": "^6.7.2",
"@tinyflow-ai/react": "^0.1.6", "@tinyflow-ai/react": "^0.1.6",

View File

@@ -14,9 +14,6 @@ importers:
'@ant-design/pro-components': '@ant-design/pro-components':
specifier: ^2.8.7 specifier: ^2.8.7
version: 2.8.7(antd@5.25.0(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 2.8.7(antd@5.25.0(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@ant-design/pro-layout':
specifier: ^7.22.4
version: 7.22.4(antd@5.25.0(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@ant-design/x': '@ant-design/x':
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0(antd@5.25.0(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.2.0(antd@5.25.0(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)

View File

@@ -1,25 +0,0 @@
import {Tinyflow} from '@tinyflow-ai/react'
import {type FormControlProps, FormItem} from 'amis'
import React from 'react'
import './Flow.scss'
import '@tinyflow-ai/react/dist/index.css'
const Flow: React.FC<FormControlProps> = props => {
const {onChange} = props
return (
<div className="flowable">
<Tinyflow
className="tinyflow-instance"
style={{height: '800px'}}
onDataChange={(value) => {
onChange(value)
}}
/>
</div>
)
}
FormItem({
type: 'flow',
autoVar: true,
})(Flow)

View File

@@ -1 +0,0 @@
import './Flow.tsx'

View File

@@ -1,10 +1,10 @@
import {createRoot} from 'react-dom/client' import {createRoot} from 'react-dom/client'
import {createHashRouter, RouterProvider} from 'react-router' import {createBrowserRouter, RouterProvider} from 'react-router'
import './components/Registry.ts' import './components/Registry.ts'
import {routes} from './route.tsx' import {routes} from './route.tsx'
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<RouterProvider router={createHashRouter(routes)}/>, <RouterProvider router={createBrowserRouter(routes)}/>,
) )

View File

@@ -1,18 +0,0 @@
import {amisRender} from '../util/amis.ts'
function Ai() {
return (
<div className="ai">
{amisRender(
{
type: 'wrapper',
body: [
'逗你的,什么都没做,哎嘿!',
],
},
)}
</div>
)
}
export default Ai

View File

@@ -28,19 +28,13 @@ const App: React.FC = () => {
route={menus} route={menus}
location={{pathname: location.pathname}} location={{pathname: location.pathname}}
menu={{type: 'sub'}} menu={{type: 'sub'}}
menuItemRender={(item, dom) => ( menuItemRender={(item, dom) => {
<div return <div onClick={() => navigate(item.path || '/')}>{dom}</div>
onClick={() => {
navigate(item.path ?? '/')
}} }}
>
{dom}
</div>
)}
fixSiderbar={true} fixSiderbar={true}
layout="mix" layout="mix"
splitMenus={true} splitMenus={true}
style={{height: '100vh'}} style={{minHeight: '100vh'}}
contentStyle={{backgroundColor: 'white', padding: '10px 10px 10px 20px'}} contentStyle={{backgroundColor: 'white', padding: '10px 10px 10px 20px'}}
> >
<Outlet/> <Outlet/>

View File

@@ -1,16 +1,7 @@
import {amisRender} from '../../util/amis.ts'
function Conversation() { function Conversation() {
return ( return (
<div className="conversation"> <div className="conversation">
{amisRender( Conversation
{
type: 'wrapper',
body: [
"逗你的,什么都没做,哎嘿!"
],
},
)}
</div> </div>
) )
} }

View File

@@ -0,0 +1,9 @@
function Inspection() {
return (
<div className="inspection">
</div>
)
}
export default Inspection

View File

@@ -79,7 +79,7 @@ const cloudCrud = (title: string, path: string) => {
const Cloud: React.FC = () => { const Cloud: React.FC = () => {
return ( return (
<div className="hudi-cloud bg-white"> <div className="hudi-cloud">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../util/amis.ts' import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../../util/amis.ts'
const color = (number: number) => { const color = (number: number) => {
let color = 'text-success' let color = 'text-success'
@@ -169,34 +169,38 @@ const tableDetailDialog = (variable: string, targetList: any) => {
const overviewYarnJob = (cluster: string, search: string, queue: string | undefined, yarnQueue: string) => { const overviewYarnJob = (cluster: string, search: string, queue: string | undefined, yarnQueue: string) => {
return { return {
className: 'font-mono', className: 'text-base leading-none',
type: 'service', type: 'table-view',
api: `${commonInfo.baseUrl}/overview/yarn-job?cluster=${cluster}&search=${search}`, border: false,
interval: 10000, padding: '0 10px 0 15px',
silentPolling: true, trs: [
body: [
{ {
type: 'tpl', tds: [
className: 'mr-1 font-bold', {
tpl: `\${PADSTART('${cluster}', 3)}`, body: `${cluster}`,
}, },
queue === undefined ? {} : { {
padding: '0px',
body: queue === undefined ? {} : {
type: 'service', type: 'service',
className: 'inline ml-2', api: `${commonInfo.baseUrl}/overview/queue?queue=${queue}`,
api: `${commonInfo.baseUrl}/overview/queue?queue=compaction-queue-${cluster}`,
interval: 10000, interval: 10000,
silentPolling: true, silentPolling: true,
body: [ body: [
{ {
type: 'tpl', type: 'tpl',
tpl: '${PADSTART(size, 2)}', tpl: '${size}',
}, },
], ],
}, },
' ', width: 100,
align: 'center',
},
{ {
padding: '0px',
width: 200,
body: {
type: 'service', type: 'service',
className: 'inline',
api: { api: {
method: 'get', method: 'get',
url: `${commonInfo.baseUrl}/overview/yarn-cluster`, url: `${commonInfo.baseUrl}/overview/yarn-cluster`,
@@ -222,38 +226,85 @@ const overviewYarnJob = (cluster: string, search: string, queue: string | undefi
}, },
interval: 10000, interval: 10000,
silentPolling: true, silentPolling: true,
body: [ body: {
'(', type: 'table-view',
border: false,
trs: [
{ {
type: 'tpl', tds: [
tpl: '<span class="font-bold ${rootUsedColor}">${PADSTART(ROUND(rootUsed), 3)}%</span>',
},
',',
{ {
body: {
type: 'tpl', type: 'tpl',
tpl: '<span class="font-bold ${targetUsedColor}">${PADSTART(ROUND(targetUsed), 3)}%</span>', tpl: '<span class="font-bold ${rootUsedColor}">${ROUND(rootUsed, 0)}%</span>',
},
width: 100,
align: 'center',
},
{
body: {
type: 'tpl',
tpl: '<span class="font-bold ${targetUsedColor}">${ROUND(targetUsed, 0)}%</span>',
},
width: 100,
align: 'center',
}, },
')',
], ],
}, },
'(', ],
{
type: 'tpl',
tpl: '<span class=\'font-bold text-cyan-300\'>${PADSTART(scheduling, 2)}</span>',
}, },
',',
{
type: 'tpl',
tpl: '<span class="font-bold text-success">${PADSTART(running, 3)}</span>',
}, },
')', },
{
padding: '0px',
width: 200,
body: {
type: 'service',
api: {
url: `${commonInfo.baseUrl}/overview/yarn-job`,
data: {
cluster: cluster,
search: search,
},
},
interval: 10000,
silentPolling: true,
body: {
type: 'table-view',
border: false,
trs: [
{
tds: [
{
body: {
type: 'tpl',
tpl: '<span class=\'font-bold text-cyan-300\'>${scheduling}</span>',
},
width: 100,
align: 'center',
},
{
body: {
type: 'tpl',
tpl: '<span class="font-bold text-success">${running}</span>',
},
width: 100,
align: 'center',
},
],
},
],
},
},
},
],
},
], ],
} }
} }
const Overview: React.FC = () => { const Overview: React.FC = () => {
return ( return (
<div className="hudi-overview bg-white"> <div className="hudi-overview">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',
@@ -346,12 +397,67 @@ const Overview: React.FC = () => {
}, },
], ],
}, },
{type: 'divider'}, {
'<span class="italic text-gray-500 my-2">集群 (集群总资源使用,队列资源使用)(调度中任务数,运行中任务数)</span>', className: 'pl-2 my-5',
type: 'wrapper',
size: 'none',
body: {
type: 'tpl',
tpl: '同步集群资源用量情况',
},
},
{
type: 'table-view',
border: false,
trs: [
{
background: '#F9F9F9',
tds: [
{
bold: true,
body: '集群',
},
{
bold: true,
body: '集群资源',
width: 100,
align: 'center',
},
{
bold: true,
body: '队列资源',
width: 100,
align: 'center',
},
{
bold: true,
body: '调度中',
width: 100,
align: 'center',
},
{
bold: true,
body: '运行中',
width: 100,
align: 'center',
},
],
},
],
},
overviewYarnJob(commonInfo.clusters.sync_names(), 'Sync', undefined, 'default'), overviewYarnJob(commonInfo.clusters.sync_names(), 'Sync', undefined, 'default'),
{type: 'divider'}, {type: 'divider'},
{ {
className: 'my-2', className: 'pl-2 my-5',
type: 'wrapper',
size: 'none',
body: [
{
type: 'tpl',
tpl: '压缩集群资源用量情况',
},
{
className: 'mt-2',
type: 'service', type: 'service',
api: `${commonInfo.baseUrl}/overview/queue?queue=compaction-queue-pre`, api: `${commonInfo.baseUrl}/overview/queue?queue=compaction-queue-pre`,
interval: 10000, interval: 10000,
@@ -363,7 +469,54 @@ const Overview: React.FC = () => {
}, },
], ],
}, },
'<span class="italic text-gray-500 my-2">集群 压缩队列任务数(集群总资源使用,队列资源使用)(调度中任务数,运行中任务数)</span>', ],
},
{
type: 'table-view',
border: false,
bold: true,
trs: [
{
background: '#F9F9F9',
tds: [
{
bold: true,
body: '集群',
},
{
bold: true,
body: '队列',
width: 100,
align: 'center',
},
{
bold: true,
body: '集群资源',
width: 100,
align: 'center',
},
{
bold: true,
body: '队列资源',
width: 100,
align: 'center',
},
{
bold: true,
body: '调度中',
width: 100,
align: 'center',
},
{
bold: true,
body: '运行中',
width: 100,
align: 'center',
},
],
},
],
},
// @ts-ignore // @ts-ignore
...Object.keys(commonInfo.clusters.compaction).map(name => overviewYarnJob(name, 'Compaction', `compaction-queue-${name}`, commonInfo.clusters.compaction[name])), ...Object.keys(commonInfo.clusters.compaction).map(name => overviewYarnJob(name, 'Compaction', `compaction-queue-${name}`, commonInfo.clusters.compaction[name])),
{type: 'divider'}, {type: 'divider'},
@@ -379,7 +532,7 @@ const Overview: React.FC = () => {
source: '${items}', source: '${items}',
...crudCommonOptions(), ...crudCommonOptions(),
headerToolbar: [ headerToolbar: [
'${version}' '${version}',
], ],
columns: [ columns: [
{ {

View File

@@ -12,7 +12,7 @@ const queueCrud = (name: string) => {
return { return {
type: 'crud', type: 'crud',
title: name, title: name,
api: `\${base}/queue/all?name=${name}`, api: `${commonInfo.baseUrl}/queue/all?name=${name}`,
...crudCommonOptions(), ...crudCommonOptions(),
interval: 10000, interval: 10000,
loadDataOnce: true, loadDataOnce: true,
@@ -79,7 +79,7 @@ const Queue = () => {
items.push(queueCrud(`compaction-queue-${name}`)) items.push(queueCrud(`compaction-queue-${name}`))
} }
return ( return (
<div className="hudi-queue bg-white overflow-y-scroll"> <div className="hudi-queue">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -18,7 +18,7 @@ import {
function Table() { function Table() {
return ( return (
<div className="hudi-table bg-white"> <div className="hudi-table">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -3,7 +3,7 @@ import {amisRender, commonInfo, paginationCommonOptions, serviceLogByAppName, ya
const Task: React.FC = () => { const Task: React.FC = () => {
return ( return (
<div className="hudi-task bg-white"> <div className="hudi-task">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -14,7 +14,7 @@ import {
const Tool: React.FC = () => { const Tool: React.FC = () => {
return ( return (
<div className="hudi-tool bg-white"> <div className="hudi-tool">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -13,7 +13,7 @@ const Yarn: React.FC = () => {
const {clusters, queue, search} = useParams() const {clusters, queue, search} = useParams()
const location = useLocation() const location = useLocation()
return ( return (
<div key={location.key} className="hudi-yarn bg-white"> <div key={location.key} className="hudi-yarn">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -3,7 +3,7 @@ import {amisRender, commonInfo, yarnQueueCrud} from '../../util/amis.ts'
const YarnCluster: React.FC = () => { const YarnCluster: React.FC = () => {
return ( return (
<div className="hudi-yarn-cluster bg-white"> <div className="hudi-yarn-cluster">
{amisRender( {amisRender(
{ {
type: 'wrapper', type: 'wrapper',

View File

@@ -4,17 +4,19 @@ import {
ClusterOutlined, ClusterOutlined,
CompressOutlined, CompressOutlined,
InfoCircleOutlined, InfoCircleOutlined,
OpenAIOutlined,
QuestionOutlined,
SunOutlined, SunOutlined,
SyncOutlined, SyncOutlined,
TableOutlined, TableOutlined,
ToolOutlined, ToolOutlined,
} from '@ant-design/icons' } from '@ant-design/icons'
import type {Route} from '@ant-design/pro-layout/es/typing' import {Navigate, type RouteObject} from 'react-router'
import type {RouteObject} from 'react-router' import Conversation from './pages/ai/Conversation.tsx'
import Ai from './pages/Ai.tsx' import Inspection from './pages/ai/Inspection.tsx'
import App from './pages/App.tsx' import App from './pages/App.tsx'
import Cloud from './pages/overview/Cloud.tsx' import Cloud from './pages/overview/Cloud.tsx'
import Overview from './pages/Overview.tsx' import Overview from './pages/overview/Overview.tsx'
import Queue from './pages/overview/Queue.tsx' import Queue from './pages/overview/Queue.tsx'
import Table from './pages/overview/Table.tsx' import Table from './pages/overview/Table.tsx'
import Task from './pages/overview/Task.tsx' import Task from './pages/overview/Task.tsx'
@@ -28,11 +30,13 @@ export const routes: RouteObject[] = [
{ {
path: '/', path: '/',
Component: App, Component: App,
children: [
{
children: [ children: [
{ {
index: true, index: true,
element: <Navigate to="/overview" replace/>,
},
{
path: 'overview',
Component: Overview, Component: Overview,
}, },
{ {
@@ -67,17 +71,28 @@ export const routes: RouteObject[] = [
path: 'task', path: 'task',
Component: Task, Component: Task,
}, },
],
},
{ {
path: 'ai', path: 'ai',
Component: Ai, children: [
{
index: true,
element: <Navigate to="/ai/inspection" replace/>,
},
{
path: 'inspection',
Component: Inspection,
},
{
path: 'conversation',
Component: Conversation,
},
],
}, },
], ],
}, },
] ]
export const menus: Route = { export const menus = {
routes: [ routes: [
{ {
path: '/', path: '/',
@@ -85,7 +100,7 @@ export const menus: Route = {
icon: <InfoCircleOutlined/>, icon: <InfoCircleOutlined/>,
routes: [ routes: [
{ {
path: '/', path: '/overview',
name: '概览', name: '概览',
icon: <InfoCircleOutlined/>, icon: <InfoCircleOutlined/>,
}, },
@@ -120,7 +135,7 @@ export const menus: Route = {
icon: <CloudOutlined/>, icon: <CloudOutlined/>,
}, },
{ {
path: '/yarn_cluster', path: 'a86f7c51-ae60-4ca4-8c4d-40b86b445a04',
name: '集群', name: '集群',
icon: <ClusterOutlined/>, icon: <ClusterOutlined/>,
routes: [ routes: [
@@ -152,6 +167,19 @@ export const menus: Route = {
{ {
path: '/ai', path: '/ai',
name: 'AI', name: 'AI',
} icon: <OpenAIOutlined/>,
routes: [
{
path: '/ai/inspection',
name: '智能巡检',
icon: <CheckSquareOutlined/>,
},
{
path: '/ai/conversation',
name: '智慧问答',
icon: <QuestionOutlined/>,
},
],
},
], ],
} }