feat(web): 完成基本适配
This commit is contained in:
90
service-web-client/.gitignore
vendored
Normal file
90
service-web-client/.gitignore
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
.idea/**
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
.idea/**/aws.xml
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
cmake-build-*/
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
*.iws
|
||||||
|
out/
|
||||||
|
.idea_modules/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
.idea/replstate.xml
|
||||||
|
.idea/sonarlint/
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
.idea/httpRequests
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
lib-cov
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
.nyc_output
|
||||||
|
.grunt
|
||||||
|
bower_components
|
||||||
|
.lock-wscript
|
||||||
|
build/Release
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
web_modules/
|
||||||
|
*.tsbuildinfo
|
||||||
|
.npm
|
||||||
|
.eslintcache
|
||||||
|
.stylelintcache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
.node_repl_history
|
||||||
|
*.tgz
|
||||||
|
.yarn-integrity
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
.cache/
|
||||||
|
.vuepress/dist
|
||||||
|
.temp
|
||||||
|
.docusaurus
|
||||||
|
.serverless/
|
||||||
|
.fusebox/
|
||||||
|
.dynamodb/
|
||||||
|
.tern-port
|
||||||
|
.vscode-test
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
21
service-web-client/index.html
Normal file
21
service-web-client/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Hudi 服务总台</title>
|
||||||
|
<style>
|
||||||
|
html, body, #root {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33
service-web-client/package.json
Normal file
33
service-web-client/package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "hudi-service-web-client",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^6.0.0",
|
||||||
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
|
"@tinyflow-ai/react": "^0.1.6",
|
||||||
|
"amis": "^6.12.0",
|
||||||
|
"antd": "^5.25.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"licia": "^1.48.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router": "^7.5.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.0",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||||
|
"globals": "^16.0.0",
|
||||||
|
"sass": "^1.87.0",
|
||||||
|
"typescript": "~5.8.3",
|
||||||
|
"typescript-eslint": "^8.30.1",
|
||||||
|
"vite": "^6.3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
6117
service-web-client/pnpm-lock.yaml
generated
Normal file
6117
service-web-client/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
service-web-client/src/components/Flow.scss
Normal file
0
service-web-client/src/components/Flow.scss
Normal file
25
service-web-client/src/components/Flow.tsx
Normal file
25
service-web-client/src/components/Flow.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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)
|
||||||
1
service-web-client/src/components/Registry.ts
Normal file
1
service-web-client/src/components/Registry.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import './Flow.tsx'
|
||||||
75
service-web-client/src/index.tsx
Normal file
75
service-web-client/src/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import {createRoot} from 'react-dom/client'
|
||||||
|
import {createBrowserRouter, RouterProvider} from 'react-router'
|
||||||
|
|
||||||
|
import './components/Registry.ts'
|
||||||
|
|
||||||
|
import App from './pages/App.tsx'
|
||||||
|
import Cloud from './pages/Cloud.tsx'
|
||||||
|
import Conversation from './pages/Conversation.tsx'
|
||||||
|
import Home from './pages/Home.tsx'
|
||||||
|
import Overview from './pages/Overview.tsx'
|
||||||
|
import Queue from './pages/Queue.tsx'
|
||||||
|
import Table from './pages/Table.tsx'
|
||||||
|
import Task from './pages/Task.tsx'
|
||||||
|
import Tool from './pages/Tool.tsx'
|
||||||
|
import Version from './pages/Version.tsx'
|
||||||
|
import Yarn from './pages/Yarn.tsx'
|
||||||
|
import YarnCluster from './pages/YarnCluster.tsx'
|
||||||
|
|
||||||
|
const routes = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
Component: App,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
Component: Home,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
Component: Overview,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/table',
|
||||||
|
Component: Table,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/queue',
|
||||||
|
Component: Queue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/version',
|
||||||
|
Component: Version,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/yarn/:clusters/:queue/:search?',
|
||||||
|
Component: Yarn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/cloud',
|
||||||
|
Component: Cloud,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/yarn_cluster',
|
||||||
|
Component: YarnCluster,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/tool',
|
||||||
|
Component: Tool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home/task',
|
||||||
|
Component: Task,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'conversation',
|
||||||
|
Component: Conversation,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<RouterProvider router={routes}/>,
|
||||||
|
)
|
||||||
55
service-web-client/src/pages/App.tsx
Normal file
55
service-web-client/src/pages/App.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import {Layout, Menu} from 'antd'
|
||||||
|
import type {ItemType, MenuItemType} from 'antd/es/menu/interface'
|
||||||
|
import {contain, isEqual} from 'licia'
|
||||||
|
import React, {useEffect, useState} from 'react'
|
||||||
|
import {NavLink, Outlet, useLocation} from 'react-router'
|
||||||
|
|
||||||
|
const {Header, Content} = Layout
|
||||||
|
|
||||||
|
const headerNav: Array<MenuItemType> = [
|
||||||
|
{key: '/', label: '首页'},
|
||||||
|
{key: '/conversation', label: 'AI'},
|
||||||
|
]
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [_, setCurrentMenu] = useState<ItemType>()
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState<Array<string>>([])
|
||||||
|
const location = useLocation()
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEqual('/', location.pathname)) {
|
||||||
|
setSelectedKeys([location.pathname])
|
||||||
|
} else {
|
||||||
|
setSelectedKeys([headerNav.filter(nav => !isEqual(nav?.key, '/')).find(nav => contain(location.pathname, nav?.key))?.key as string ?? '/'])
|
||||||
|
}
|
||||||
|
setCurrentMenu(headerNav.find(nav => isEqual(nav?.key, location.pathname)))
|
||||||
|
}, [location])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout className="app h-full">
|
||||||
|
<Header className="header flex items-center p-5">
|
||||||
|
<div className="title font-sans text-white flex flex-col mr-10">
|
||||||
|
<div className="main-title text-xl font-extrabold leading-normal">Hudi 服务总台</div>
|
||||||
|
<div className="sub-title text-gray-300 font-bold leading-normal">Hudi 全链路服务监控和控制台</div>
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
className="header-nav"
|
||||||
|
theme="dark"
|
||||||
|
mode="horizontal"
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
items={headerNav.map(nav => ({
|
||||||
|
key: nav.key,
|
||||||
|
label: <NavLink className="font-bold" to={nav.key as string}>{nav.label}</NavLink>,
|
||||||
|
}))}
|
||||||
|
style={{minWidth: 0, flex: 'auto'}}
|
||||||
|
/>
|
||||||
|
</Header>
|
||||||
|
<Layout>
|
||||||
|
<Content className="content">
|
||||||
|
<Outlet/>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
96
service-web-client/src/pages/Cloud.tsx
Normal file
96
service-web-client/src/pages/Cloud.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
amisRender,
|
||||||
|
commonInfo,
|
||||||
|
crudCommonOptions,
|
||||||
|
serviceLogByAppName,
|
||||||
|
serviceLogByAppNameAndHost,
|
||||||
|
time,
|
||||||
|
} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const cloudCrud = (title: string, path: string) => {
|
||||||
|
return {
|
||||||
|
type: 'crud',
|
||||||
|
title: title,
|
||||||
|
api: `${commonInfo.baseUrl}${path}`,
|
||||||
|
...crudCommonOptions(),
|
||||||
|
interval: 2000,
|
||||||
|
headerToolbar: ['reload'],
|
||||||
|
loadDataOnce: true,
|
||||||
|
perPage: 100,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: '名称',
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: `\${name} \${IF(size === undefined, '', '<span class="font-bold label label-primary">' + size + '</span>')}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: '状态',
|
||||||
|
align: 'center',
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'serviceUpTime',
|
||||||
|
label: '启动时间',
|
||||||
|
...time('serviceUpTime'),
|
||||||
|
align: 'center',
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{name: 'url', label: '地址'},
|
||||||
|
{
|
||||||
|
type: 'operation',
|
||||||
|
label: '操作',
|
||||||
|
width: 100,
|
||||||
|
fixed: 'right',
|
||||||
|
className: 'nowrap',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: '日志',
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
tooltip: '打开Grafana日志',
|
||||||
|
onEvent: {
|
||||||
|
click: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: 'custom',
|
||||||
|
// @ts-ignore
|
||||||
|
script: (context, doAction, event) => {
|
||||||
|
let data = context.props.data
|
||||||
|
let url = ''
|
||||||
|
if (data['metadata']) {
|
||||||
|
url = serviceLogByAppNameAndHost(data.serviceId, data.metadata.hostname)
|
||||||
|
} else if (data['name']) {
|
||||||
|
url = serviceLogByAppName(data.name)
|
||||||
|
}
|
||||||
|
window.open(url, '_blank')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cloud: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="hudi-cloud bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
cloudCrud('服务列表', '/cloud/list'),
|
||||||
|
cloudCrud('服务列表 (IP)', '/cloud/list_ip'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Cloud
|
||||||
18
service-web-client/src/pages/Conversation.tsx
Normal file
18
service-web-client/src/pages/Conversation.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import {amisRender} from '../util/amis.ts'
|
||||||
|
|
||||||
|
function Conversation() {
|
||||||
|
return (
|
||||||
|
<div className="conversation">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
"逗你的,什么都没做,哎嘿!"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Conversation
|
||||||
83
service-web-client/src/pages/Home.tsx
Normal file
83
service-web-client/src/pages/Home.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
CheckSquareOutlined,
|
||||||
|
CloudOutlined,
|
||||||
|
ClusterOutlined,
|
||||||
|
CompressOutlined,
|
||||||
|
InfoCircleOutlined,
|
||||||
|
SunOutlined,
|
||||||
|
SyncOutlined,
|
||||||
|
TableOutlined,
|
||||||
|
ToolOutlined,
|
||||||
|
} from '@ant-design/icons'
|
||||||
|
import {Layout, Menu, theme} from 'antd'
|
||||||
|
import type {ItemType} from 'antd/es/menu/interface'
|
||||||
|
import {isNil} from 'licia'
|
||||||
|
import React from 'react'
|
||||||
|
import {NavLink, Outlet} from 'react-router'
|
||||||
|
import {commonInfo} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const {Sider, Content} = Layout
|
||||||
|
|
||||||
|
const generateNavItem: any = (key: string, label: string, icon?: any) => {
|
||||||
|
let nav: any = {
|
||||||
|
key: key,
|
||||||
|
label: <NavLink to={key}>{label}</NavLink>,
|
||||||
|
}
|
||||||
|
if (!isNil(icon)) {
|
||||||
|
nav['icon'] = icon
|
||||||
|
}
|
||||||
|
return nav
|
||||||
|
}
|
||||||
|
const siderNav: ItemType[] = [
|
||||||
|
generateNavItem('/', '概览', <InfoCircleOutlined/>),
|
||||||
|
generateNavItem('/home/table', '表任务', <TableOutlined/>),
|
||||||
|
generateNavItem('/home/queue', '压缩队列', <CompressOutlined/>),
|
||||||
|
generateNavItem('/home/version', '跨天', <SunOutlined/>),
|
||||||
|
generateNavItem(`/home/yarn/${commonInfo.clusters.sync_names()}/root/Sync`, '同步集群', <SyncOutlined/>),
|
||||||
|
generateNavItem(
|
||||||
|
`/home/yarn/${commonInfo.clusters.compaction_names()}/default/Compaction`,
|
||||||
|
'压缩集群',
|
||||||
|
<SyncOutlined/>,
|
||||||
|
),
|
||||||
|
generateNavItem('/home/cloud', '服务', <CloudOutlined/>),
|
||||||
|
{
|
||||||
|
label: '集群',
|
||||||
|
icon: <ClusterOutlined/>,
|
||||||
|
children: [
|
||||||
|
generateNavItem('/home/yarn_cluster', '总览', <InfoCircleOutlined/>),
|
||||||
|
...Object.keys(commonInfo.clusters.compaction).map(name => generateNavItem(
|
||||||
|
// @ts-ignore
|
||||||
|
`/home/yarn/${name}/${commonInfo.clusters.compaction[name]}`,
|
||||||
|
`${name} 集群`,
|
||||||
|
<ClusterOutlined/>,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
generateNavItem('/home/tool', '工具', <ToolOutlined/>),
|
||||||
|
generateNavItem('/home/task', '任务', <CheckSquareOutlined/>),
|
||||||
|
]
|
||||||
|
|
||||||
|
const Home: React.FC = () => {
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
colorBgContainer,
|
||||||
|
},
|
||||||
|
} = theme.useToken()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout className="home min-h-full">
|
||||||
|
<Sider width={170} style={{backgroundColor: colorBgContainer}}>
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
defaultSelectedKeys={['/']}
|
||||||
|
items={siderNav}
|
||||||
|
/>
|
||||||
|
</Sider>
|
||||||
|
<Content className="p-3">
|
||||||
|
<Outlet/>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
||||||
456
service-web-client/src/pages/Overview.tsx
Normal file
456
service-web-client/src/pages/Overview.tsx
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const color = (number: number) => {
|
||||||
|
let color = 'text-success'
|
||||||
|
if (number > 30) {
|
||||||
|
color = 'text-primary'
|
||||||
|
}
|
||||||
|
if (number > 90) {
|
||||||
|
color = 'text-danger'
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionDetailDialog = (variable: string, target: string) => {
|
||||||
|
return {
|
||||||
|
disabledOn: `${variable} === 0`,
|
||||||
|
type: 'action',
|
||||||
|
label: '详情',
|
||||||
|
level: 'link',
|
||||||
|
size: 'sm',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '详情',
|
||||||
|
actions: [],
|
||||||
|
size: 'md',
|
||||||
|
closeOnEsc: false,
|
||||||
|
closeOnOutside: false,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/overview/version_detail`,
|
||||||
|
data: {
|
||||||
|
target: `${target}`,
|
||||||
|
version: '${version}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'table',
|
||||||
|
source: '${items}',
|
||||||
|
affixHeader: false,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: 'Flink job id',
|
||||||
|
fixed: 'left',
|
||||||
|
type: 'wrapper',
|
||||||
|
size: 'none',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${id}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${id}',
|
||||||
|
tooltip: '复制 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '别名',
|
||||||
|
type: 'wrapper',
|
||||||
|
fixed: 'left',
|
||||||
|
size: 'none',
|
||||||
|
className: 'nowrap',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${alias}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${alias}',
|
||||||
|
tooltip: '复制别名',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableDetailDialog = (variable: string, targetList: any) => {
|
||||||
|
return {
|
||||||
|
disabledOn: `${variable} === 0`,
|
||||||
|
type: 'action',
|
||||||
|
label: '详情',
|
||||||
|
level: 'link',
|
||||||
|
size: 'sm',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '详情',
|
||||||
|
size: 'md',
|
||||||
|
...readOnlyDialogOptions(),
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'table',
|
||||||
|
source: `\${${targetList}}`,
|
||||||
|
affixHeader: false,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: 'Flink job id',
|
||||||
|
fixed: 'left',
|
||||||
|
type: 'wrapper',
|
||||||
|
size: 'none',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${id}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${id}',
|
||||||
|
tooltip: '复制 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '别名',
|
||||||
|
type: 'wrapper',
|
||||||
|
fixed: 'left',
|
||||||
|
size: 'none',
|
||||||
|
className: 'nowrap',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${alias}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${alias}',
|
||||||
|
tooltip: '复制别名',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const overviewYarnJob = (cluster: string, search: string, queue: string | undefined, yarnQueue: string) => {
|
||||||
|
return {
|
||||||
|
className: 'font-mono',
|
||||||
|
type: 'service',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/yarn-job?cluster=${cluster}&search=${search}`,
|
||||||
|
interval: 10000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
className: 'mr-1 font-bold',
|
||||||
|
tpl: `\${PADSTART('${cluster}', 3)}`,
|
||||||
|
},
|
||||||
|
queue === undefined ? {} : {
|
||||||
|
type: 'service',
|
||||||
|
className: 'inline ml-2',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/queue?queue=compaction-queue-${cluster}`,
|
||||||
|
interval: 10000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${PADSTART(size, 2)}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
' ',
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
className: 'inline',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/overview/yarn-cluster`,
|
||||||
|
data: {
|
||||||
|
cluster: cluster,
|
||||||
|
queue: yarnQueue,
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
adaptor: function (payload, response) {
|
||||||
|
let rootUsed = (payload['data']['root']['usedCapacity'] * 100 / payload['data']['root']['capacity'])
|
||||||
|
let targetUsed = (payload['data']['target']['absoluteUsedCapacity'] * 100 / payload['data']['target']['absoluteMaxCapacity'])
|
||||||
|
return {
|
||||||
|
...payload,
|
||||||
|
data: {
|
||||||
|
...payload.data,
|
||||||
|
rootUsed: rootUsed,
|
||||||
|
rootUsedColor: color(rootUsed),
|
||||||
|
targetUsed: targetUsed,
|
||||||
|
targetUsedColor: color(targetUsed),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interval: 10000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
'(',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold ${rootUsedColor}">${PADSTART(ROUND(rootUsed), 3)}%</span>',
|
||||||
|
},
|
||||||
|
',',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold ${targetUsedColor}">${PADSTART(ROUND(targetUsed), 3)}%</span>',
|
||||||
|
},
|
||||||
|
')',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'(',
|
||||||
|
{
|
||||||
|
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>',
|
||||||
|
},
|
||||||
|
')',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Overview: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="hudi-overview bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
// language=JavaScript
|
||||||
|
dataProvider: 'const timer = setInterval(() => {\n setData({date: new Date().toLocaleString()})\n}, 1000)\nreturn () => {\n clearInterval(timer)\n}',
|
||||||
|
body: [
|
||||||
|
'当前时间:',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
className: 'font-bold',
|
||||||
|
tpl: '${date}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{type: 'divider'},
|
||||||
|
'<span class="italic text-gray-500 my-2">表数量 (重点表数量, 普通表数量)</span>',
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
api: `${commonInfo.baseUrl}/overview`,
|
||||||
|
interval: 60000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '逻辑表:<span class="font-bold mr-1 font-mono">${PADSTART(table_count, 4)} (<span class="text-primary">${PADSTART(table_focus_count, 4)}</span>, ${PADSTART(table_count - table_focus_count, 4)})</span>',
|
||||||
|
},
|
||||||
|
'<br>',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '湖底表:<span class="font-bold mr-1 font-mono">${PADSTART(hudi_count, 4)} (<span class="text-primary">${PADSTART(hudi_focus_count, 4)}</span>, ${PADSTART(hudi_count - hudi_focus_count, 4)})</span>',
|
||||||
|
},
|
||||||
|
'<br>',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '嗨福表:<span class="font-bold mr-1 font-mono">${PADSTART(hive_count, 4)} (<span class="text-primary">${PADSTART(hive_focus_count, 4)}</span>, ${PADSTART(hive_count - hive_focus_count, 4)})</span>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{type: 'divider'},
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/sync_running_status`,
|
||||||
|
interval: 10000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '任务数<span class="font-bold m-2 font-mono">${totalJob}</span>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'mx-2',
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '运行中<span class="font-bold m-2 font-mono">${PADSTART(runningJob, 3)}</span>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '已停止<span class="font-bold m-2 font-mono text-danger">${PADSTART(unRunningJob, 3)}</span>',
|
||||||
|
},
|
||||||
|
tableDetailDialog('unRunningJob', 'unRunningJobList'),
|
||||||
|
'<br/>',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '总表数<span class="font-bold m-2 font-mono">${totalTable}</span>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'mx-2',
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '运行中<span class="font-bold m-2 font-mono">${PADSTART(runningTable, 3)}</span>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '已停止<span class="font-bold m-2 font-mono text-danger">${PADSTART(unRunningTable, 3)}</span>',
|
||||||
|
},
|
||||||
|
tableDetailDialog('unRunningTable', 'unRunningTableList'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{type: 'divider'},
|
||||||
|
'<span class="italic text-gray-500 my-2">集群 (集群总资源使用,队列资源使用)(调度中任务数,运行中任务数)</span>',
|
||||||
|
overviewYarnJob(commonInfo.clusters.sync_names(), 'Sync', undefined, 'default'),
|
||||||
|
{type: 'divider'},
|
||||||
|
{
|
||||||
|
className: 'my-2',
|
||||||
|
type: 'service',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/queue?queue=compaction-queue-pre`,
|
||||||
|
interval: 10000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '预调度队列:<span class="font-bold">${size}</span>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'<span class="italic text-gray-500 my-2">集群 压缩队列任务数(集群总资源使用,队列资源使用)(调度中任务数,运行中任务数)</span>',
|
||||||
|
// @ts-ignore
|
||||||
|
...Object.keys(commonInfo.clusters.compaction).map(name => overviewYarnJob(name, 'Compaction', `compaction-queue-${name}`, commonInfo.clusters.compaction[name])),
|
||||||
|
{type: 'divider'},
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/version`,
|
||||||
|
interval: 10000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
'版本:',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
className: 'font-bold',
|
||||||
|
tpl: '${version}',
|
||||||
|
},
|
||||||
|
'<br/>',
|
||||||
|
'<span class="italic text-gray-500 my-2">未接收, 未跨天</span>',
|
||||||
|
'<br/>',
|
||||||
|
'重点表:',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold font-mono">${PADSTART(unReceive.focus, 3)}</span>',
|
||||||
|
},
|
||||||
|
versionDetailDialog('unReceive.focus', 'unReceive_focus'),
|
||||||
|
',',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold font-mono">${PADSTART(unSchedule.focus, 3)}</span>',
|
||||||
|
},
|
||||||
|
versionDetailDialog('unSchedule.focus', 'unScheduled_focus'),
|
||||||
|
'<br/>',
|
||||||
|
'普通表:',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold font-mono">${PADSTART(unReceive.normal, 3)}</span>',
|
||||||
|
},
|
||||||
|
versionDetailDialog('unReceive.normal', 'unReceive_normal'),
|
||||||
|
',',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold font-mono">${PADSTART(unSchedule.normal, 3)}</span>',
|
||||||
|
},
|
||||||
|
versionDetailDialog('unSchedule.normal', 'unScheduled_normal'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{type: 'divider'},
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/schedule_jobs`,
|
||||||
|
interval: 60000,
|
||||||
|
silentPolling: true,
|
||||||
|
body: [
|
||||||
|
'调度策略',
|
||||||
|
{
|
||||||
|
type: 'each',
|
||||||
|
name: 'items',
|
||||||
|
items: {
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<div class="font-mono"><span class="font-bold">${trigger}</span> <span class="text-gray-500">(${job})</span></div>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{type: 'divider'},
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
title: '监控指标运行进度',
|
||||||
|
api: `${commonInfo.baseUrl}/overview/monitor_progress`,
|
||||||
|
...crudCommonOptions(),
|
||||||
|
interval: 2000,
|
||||||
|
loadDataOnce: true,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'running',
|
||||||
|
label: '状态',
|
||||||
|
type: 'mapping',
|
||||||
|
width: 50,
|
||||||
|
map: {
|
||||||
|
'true': '运行中',
|
||||||
|
'false': '未运行',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '进度',
|
||||||
|
type: 'progress',
|
||||||
|
value: '${ROUND(progress * 100)}',
|
||||||
|
map: 'bg-primary',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Overview
|
||||||
96
service-web-client/src/pages/Queue.tsx
Normal file
96
service-web-client/src/pages/Queue.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
amisRender,
|
||||||
|
commonInfo,
|
||||||
|
copyField,
|
||||||
|
crudCommonOptions,
|
||||||
|
paginationCommonOptions,
|
||||||
|
time,
|
||||||
|
yarnQueueCrud,
|
||||||
|
} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const queueCrud = (name: string) => {
|
||||||
|
return {
|
||||||
|
type: 'crud',
|
||||||
|
title: name,
|
||||||
|
api: `\${base}/queue/all?name=${name}`,
|
||||||
|
...crudCommonOptions(),
|
||||||
|
interval: 10000,
|
||||||
|
loadDataOnce: true,
|
||||||
|
perPage: 5,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
'filter-toggler',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '共 <span class=\'text-primary font-bold\'>${total|default:0}</span> 个任务',
|
||||||
|
},
|
||||||
|
paginationCommonOptions(false),
|
||||||
|
],
|
||||||
|
footerToolbar: [],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'data.flinkJobId',
|
||||||
|
label: '任务 ID',
|
||||||
|
width: 190,
|
||||||
|
...copyField('data.flinkJobId'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'data.alias',
|
||||||
|
label: '别名',
|
||||||
|
className: 'nowrap',
|
||||||
|
...copyField('data.alias'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'data.batch',
|
||||||
|
label: '批次',
|
||||||
|
width: 100,
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="label label-warning">${data.batch}</span>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
label: '优先级',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="label bg-info">${priority}</span>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'data.comment',
|
||||||
|
label: '备注',
|
||||||
|
className: 'nowrap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createTime',
|
||||||
|
label: '任务提交时间',
|
||||||
|
...time('createTime'),
|
||||||
|
width: 160,
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Queue = () => {
|
||||||
|
let items = []
|
||||||
|
for (let name of Object.keys(commonInfo.clusters.compaction)) {
|
||||||
|
// @ts-ignore
|
||||||
|
items.push(yarnQueueCrud(name, commonInfo.clusters.compaction[name]))
|
||||||
|
items.push(queueCrud(`compaction-queue-${name}`))
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="hudi-queue bg-white overflow-y-scroll">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
queueCrud('compaction-queue-pre'),
|
||||||
|
...items,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Queue
|
||||||
256
service-web-client/src/pages/Table.tsx
Normal file
256
service-web-client/src/pages/Table.tsx
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
import {
|
||||||
|
aliasTextInput,
|
||||||
|
amisRender,
|
||||||
|
commonInfo,
|
||||||
|
compactionStatusMapping,
|
||||||
|
crudCommonOptions,
|
||||||
|
filterableField,
|
||||||
|
flinkJobDialog,
|
||||||
|
flinkJobIdTextInput,
|
||||||
|
hudiTableTypeMapping,
|
||||||
|
mappingField,
|
||||||
|
paginationCommonOptions,
|
||||||
|
runModeMapping,
|
||||||
|
tableMetaDialog,
|
||||||
|
tableRunningStateMapping,
|
||||||
|
timeAndFrom,
|
||||||
|
} from '../util/amis.ts'
|
||||||
|
|
||||||
|
function Table() {
|
||||||
|
return (
|
||||||
|
<div className="hudi-table bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
size: 'none',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
id: 'table-service',
|
||||||
|
type: 'service',
|
||||||
|
data: {},
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/table/list`,
|
||||||
|
data: {
|
||||||
|
page: '${page|default:undefined}',
|
||||||
|
count: '${perPage|default:undefined}',
|
||||||
|
order: '${orderBy|default:undefined}',
|
||||||
|
direction: '${orderDir|default:undefined}',
|
||||||
|
search_flink_job_id: '${flinkJobId|default:undefined}',
|
||||||
|
search_alias: '${alias|default:undefined}',
|
||||||
|
filter_hudi_table_type: '${tableMeta\\.hudi\\.targetTableType|default:undefined}',
|
||||||
|
filter_run_mode: '${flinkJob\\.runMode|default:undefined}',
|
||||||
|
filter_compaction_status: '${syncState\\.compactionStatus|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...crudCommonOptions(),
|
||||||
|
// interval: 10000,
|
||||||
|
filter: {
|
||||||
|
title: '表筛选',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
...flinkJobIdTextInput('58d0da94-1b3c-4234-948d-482ae3425e70'),
|
||||||
|
size: 'lg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...aliasTextInput('58d0da94-1b3c-4234-948d-482ae3425e70'),
|
||||||
|
size: 'lg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
level: 'primary',
|
||||||
|
label: '查询',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'reset',
|
||||||
|
label: '重置',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
filterTogglable: true,
|
||||||
|
filterDefaultVisible: true,
|
||||||
|
perPage: 20,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
'filter-toggler',
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: 'Flink job id',
|
||||||
|
width: 195,
|
||||||
|
fixed: 'left',
|
||||||
|
type: 'wrapper',
|
||||||
|
size: 'none',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '${flinkJobId}',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'dialog',
|
||||||
|
tooltip: '查看详情',
|
||||||
|
dialog: flinkJobDialog(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${flinkJobId}',
|
||||||
|
tooltip: '复制 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '别名',
|
||||||
|
type: 'wrapper',
|
||||||
|
fixed: 'left',
|
||||||
|
size: 'none',
|
||||||
|
className: 'nowrap',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '${tableMeta.alias}',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'dialog',
|
||||||
|
tooltip: '查看详情',
|
||||||
|
dialog: tableMetaDialog(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${tableMeta.alias}',
|
||||||
|
tooltip: '复制别名',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tableMeta.hudi.targetTableType',
|
||||||
|
label: '表类型',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
...mappingField('tableMeta.hudi.targetTableType', hudiTableTypeMapping),
|
||||||
|
filterable: filterableField(hudiTableTypeMapping, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'flinkJob.runMode',
|
||||||
|
label: '任务类型',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
...mappingField('flinkJob.runMode', runModeMapping),
|
||||||
|
filterable: filterableField(runModeMapping, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'syncRunning',
|
||||||
|
label: '同步运行状态',
|
||||||
|
align: 'center',
|
||||||
|
...mappingField('syncRunning', tableRunningStateMapping),
|
||||||
|
className: 'bg-green-50',
|
||||||
|
width: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source_start_time',
|
||||||
|
label: '同步启动时间',
|
||||||
|
...timeAndFrom('syncState.sourceStartTime', 'syncState.sourceStartTimeFromNow', '未启动'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-green-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source_receive_time',
|
||||||
|
label: '同步接收时间',
|
||||||
|
...timeAndFrom('syncState.sourceReceiveTime', 'syncState.sourceReceiveTimeFromNow', '无数据'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-green-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source_checkpoint_time',
|
||||||
|
label: '同步心跳时间',
|
||||||
|
...timeAndFrom('syncState.sourceCheckpointTime', 'syncState.sourceCheckpointTimeFromNow', '未启动'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-green-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source_publish_time',
|
||||||
|
label: '源端发布时间',
|
||||||
|
...timeAndFrom('syncState.sourcePublishTime', 'syncState.sourcePublishTimeFromNow', '无增量'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-green-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source_operation_time',
|
||||||
|
label: '源端业务时间',
|
||||||
|
...timeAndFrom('syncState.sourceOperationTime', 'syncState.sourceOperationTimeFromNow', '无增量'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-green-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'compactionRunning',
|
||||||
|
label: '压缩运行状态',
|
||||||
|
align: 'center',
|
||||||
|
...mappingField('compactionRunning', tableRunningStateMapping),
|
||||||
|
className: 'bg-cyan-50',
|
||||||
|
width: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'syncState.compactionStatus',
|
||||||
|
label: '压缩状态',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
...mappingField('syncState.compactionStatus', compactionStatusMapping),
|
||||||
|
filterable: filterableField(compactionStatusMapping, true),
|
||||||
|
className: 'bg-cyan-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'compaction_start_time',
|
||||||
|
label: '压缩启动时间',
|
||||||
|
...timeAndFrom('syncState.compactionStartTime', 'syncState.compactionStartTimeFromNow'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-cyan-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'compaction_latest_operation_time',
|
||||||
|
label: '压缩业务时间',
|
||||||
|
...timeAndFrom('syncState.compactionLatestOperationTime', 'syncState.compactionLatestOperationTimeFromNow', '无'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-cyan-50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'compaction_finish_time',
|
||||||
|
label: '压缩完成时间',
|
||||||
|
...timeAndFrom('syncState.compactionFinishTime', 'syncState.compactionFinishTimeFromNow'),
|
||||||
|
sortable: true,
|
||||||
|
className: 'bg-cyan-50',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Table
|
||||||
195
service-web-client/src/pages/Task.tsx
Normal file
195
service-web-client/src/pages/Task.tsx
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {amisRender, commonInfo, paginationCommonOptions, serviceLogByAppName, yarnCrudColumns} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const Task: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="hudi-task bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: '检索文件',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
label: '提交任务',
|
||||||
|
actionType: 'ajax',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/task/scan`,
|
||||||
|
data: {
|
||||||
|
key: '${key|default:undefined}',
|
||||||
|
hdfs: '${hdfs|default:undefined}',
|
||||||
|
pulsar: '${pulsar|default:undefined}',
|
||||||
|
topic: '${topic|default:undefined}',
|
||||||
|
mode: '${scan_mode|default:undefined}',
|
||||||
|
fields: '${fields|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
name: 'scan_mode',
|
||||||
|
type: 'checkboxes',
|
||||||
|
label: '检索范围',
|
||||||
|
checkAll: true,
|
||||||
|
required: true,
|
||||||
|
value: 'log',
|
||||||
|
options: [
|
||||||
|
{label: '消息队列', value: 'queue'},
|
||||||
|
{label: '日志文件', value: 'log'},
|
||||||
|
{label: '数据文件', value: 'base'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'key',
|
||||||
|
label: '检索字段',
|
||||||
|
required: true,
|
||||||
|
clearable: true,
|
||||||
|
description: '检索带有该字符的记录',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'hdfs',
|
||||||
|
label: 'HDFS路经',
|
||||||
|
requiredOn: '${CONTAINS(scan_mode, \'log\') || CONTAINS(scan_mode, \'base\')}',
|
||||||
|
visibleOn: '${CONTAINS(scan_mode, \'log\') || CONTAINS(scan_mode, \'base\')}',
|
||||||
|
clearable: true,
|
||||||
|
description: '输入表HDFS路径',
|
||||||
|
autoComplete: `${commonInfo.baseUrl}/table/all_hdfs?key=$term`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'fields',
|
||||||
|
label: '指定字段',
|
||||||
|
visibleOn: '${CONTAINS(scan_mode, \'base\')}',
|
||||||
|
clearable: true,
|
||||||
|
description: '逗号分隔,可以大幅提高parquet文件检索速度,但无法获取指定字段外的字段内容',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'topic',
|
||||||
|
label: 'Pulsar主题',
|
||||||
|
requiredOn: '${CONTAINS(scan_mode, \'queue\')}',
|
||||||
|
visibleOn: '${CONTAINS(scan_mode, \'queue\')}',
|
||||||
|
clearable: true,
|
||||||
|
description: '输入Pulsar主题',
|
||||||
|
autoComplete: `${commonInfo.baseUrl}/table/all_pulsar_topic?key=$term`,
|
||||||
|
columnRatio: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'pulsar',
|
||||||
|
label: 'Pulsar地址',
|
||||||
|
requiredOn: '${CONTAINS(scan_mode, \'queue\')}',
|
||||||
|
visibleOn: '${CONTAINS(scan_mode, \'queue\')}',
|
||||||
|
clearable: true,
|
||||||
|
description: '输入Pulsar地址',
|
||||||
|
autoComplete: `${commonInfo.baseUrl}/table/all_pulsar?key=$term`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: '综合查询',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '总数&最后操作时间',
|
||||||
|
actionType: 'ajax',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/task/table_summary`,
|
||||||
|
data: {
|
||||||
|
hdfs: '${hdfs|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '最后10条记录',
|
||||||
|
actionType: 'ajax',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/task/table_sampling`,
|
||||||
|
data: {
|
||||||
|
hdfs: '${hdfs|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'hdfs',
|
||||||
|
label: 'HDFS路经',
|
||||||
|
required: true,
|
||||||
|
clearable: true,
|
||||||
|
description: '输入表HDFS路径',
|
||||||
|
autoComplete: `${commonInfo.baseUrl}/table/all_hdfs?key=$term`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/yarn/job_list`,
|
||||||
|
data: {
|
||||||
|
clusters: commonInfo.clusters.sync_names(),
|
||||||
|
page: '${page|default:undefined}',
|
||||||
|
count: '${perPage|default:undefined}',
|
||||||
|
order: '${orderBy|default:undefined}',
|
||||||
|
direction: '${orderDir|default:undefined}',
|
||||||
|
filter_state: '${state|default:undefined}',
|
||||||
|
filter_final_status: '${finalStatus|default:undefined}',
|
||||||
|
search_id: '${id|default:undefined}',
|
||||||
|
search_name: 'Service_Task',
|
||||||
|
precise: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
affixHeader: false,
|
||||||
|
interval: 10000,
|
||||||
|
syncLocation: false,
|
||||||
|
silentPolling: true,
|
||||||
|
resizable: false,
|
||||||
|
perPage: 10,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
{
|
||||||
|
label: '任务管理器日志',
|
||||||
|
type: 'action',
|
||||||
|
tooltip: '打开Grafana日志',
|
||||||
|
onEvent: {
|
||||||
|
click: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: 'custom',
|
||||||
|
script: () => window.open(serviceLogByAppName('service-executor-manager'), '_blank'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
footerToolbar: [],
|
||||||
|
columns: yarnCrudColumns(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Task
|
||||||
316
service-web-client/src/pages/Tool.tsx
Normal file
316
service-web-client/src/pages/Tool.tsx
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
aliasTextInput,
|
||||||
|
amisRender,
|
||||||
|
commonInfo,
|
||||||
|
crudCommonOptions,
|
||||||
|
flinkJobIdTextInput,
|
||||||
|
formReloadFlinkJobIdTextInputAndAliasTextInput,
|
||||||
|
hdfsDialog,
|
||||||
|
paginationCommonOptions,
|
||||||
|
readOnlyDialogOptions,
|
||||||
|
timelineColumns,
|
||||||
|
} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const Tool: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="hudi-tool bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'panel',
|
||||||
|
title: '乱七八糟小工具',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: 'SQL日志',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '日志',
|
||||||
|
...readOnlyDialogOptions(),
|
||||||
|
size: 'lg',
|
||||||
|
body: {
|
||||||
|
type: 'crud',
|
||||||
|
api: `${commonInfo.baseUrl}/log/query_sql_log`,
|
||||||
|
...crudCommonOptions(),
|
||||||
|
loadDataOnce: true,
|
||||||
|
perPage: 50,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
paginationCommonOptions(undefined, 10),
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationCommonOptions(undefined, 10),
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'sql',
|
||||||
|
label: 'SQL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createTime',
|
||||||
|
label: '执行时间',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: 'ZK节点',
|
||||||
|
className: 'ml-2',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '日志',
|
||||||
|
...readOnlyDialogOptions(),
|
||||||
|
size: 'lg',
|
||||||
|
body: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: 'HDFS文件管理器',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: '直接下载',
|
||||||
|
type: 'action',
|
||||||
|
onEvent: {
|
||||||
|
click: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: 'custom',
|
||||||
|
// @ts-ignore
|
||||||
|
script: (context, action, event) => {
|
||||||
|
let downloadUrl = `${event.data.base}/hudi/hdfs_download?root=${encodeURI(event.data.hdfs)}`
|
||||||
|
window.open(downloadUrl, '_blank')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
label: '查看',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: hdfsDialog('hdfs'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'hdfs',
|
||||||
|
label: 'HDFS根路经',
|
||||||
|
required: true,
|
||||||
|
clearable: true,
|
||||||
|
description: '输入表HDFS路径',
|
||||||
|
autoComplete: `${commonInfo.baseUrl}/table/all_hdfs?key=$term`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: '查询时间线',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
label: '查询时间线',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: 'Hudi 表时间线',
|
||||||
|
actions: [],
|
||||||
|
size: 'lg',
|
||||||
|
body: {
|
||||||
|
type: 'crud',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/hudi/timeline/list_hdfs`,
|
||||||
|
data: {
|
||||||
|
page: '${page|default:undefined}',
|
||||||
|
count: '${perPage|default:undefined}',
|
||||||
|
order: '${orderBy|default:undefined}',
|
||||||
|
direction: '${orderDir|default:undefined}',
|
||||||
|
hdfs: '${hdfs|default:undefined}',
|
||||||
|
filter_type: '${type|default:active}',
|
||||||
|
filter_action: '${action|default:undefined}',
|
||||||
|
filter_state: '${state|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...crudCommonOptions(),
|
||||||
|
perPage: 50,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
paginationCommonOptions(undefined, 10),
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationCommonOptions(undefined, 10),
|
||||||
|
],
|
||||||
|
columns: timelineColumns(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
label: '查询表结构',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: 'Hudi 表结构',
|
||||||
|
actions: [],
|
||||||
|
size: 'lg',
|
||||||
|
body: {
|
||||||
|
type: 'service',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/hudi/schema`,
|
||||||
|
data: {
|
||||||
|
hdfs: '${hdfs|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'page',
|
||||||
|
body: {
|
||||||
|
type: 'json',
|
||||||
|
source: '${detail}',
|
||||||
|
levelExpand: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'hdfs',
|
||||||
|
label: 'HDFS路经',
|
||||||
|
required: true,
|
||||||
|
clearable: true,
|
||||||
|
description: '输入表HDFS路径',
|
||||||
|
autoComplete: `${commonInfo.baseUrl}/table/all_hdfs?key=$term`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: '提交压缩任务',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/schedule/table`,
|
||||||
|
data: {
|
||||||
|
flink_job_id: '${flinkJobId|default:undefined}',
|
||||||
|
alias: '${alias|default:undefined}',
|
||||||
|
recommend: '${recommend === \'undefined\' ? undefined : recommend|default:undefined}',
|
||||||
|
force: '${force === \'undefined\' ? undefined : force|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...formReloadFlinkJobIdTextInputAndAliasTextInput('0fe6a96c-6b6e-4346-b18e-c631c2389f48'),
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
flinkJobIdTextInput('0fe6a96c-6b6e-4346-b18e-c631c2389f48', true),
|
||||||
|
aliasTextInput('0fe6a96c-6b6e-4346-b18e-c631c2389f48', true),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
name: 'recommend',
|
||||||
|
type: 'radios',
|
||||||
|
label: '优先指定集群',
|
||||||
|
selectFirst: true,
|
||||||
|
options: [
|
||||||
|
{label: '无', value: 'undefined'},
|
||||||
|
...Object.keys(commonInfo.clusters.compaction)
|
||||||
|
.map(name => {
|
||||||
|
return {label: name, value: name}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'force',
|
||||||
|
type: 'radios',
|
||||||
|
label: '强制指定集群',
|
||||||
|
selectFirst: true,
|
||||||
|
options: [
|
||||||
|
{label: '无', value: 'undefined'},
|
||||||
|
...Object.keys(commonInfo.clusters.compaction)
|
||||||
|
.map(name => {
|
||||||
|
return {label: name, value: name}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: '批量提交压缩任务',
|
||||||
|
api: {
|
||||||
|
method: 'post',
|
||||||
|
url: `${commonInfo.baseUrl}/schedule/table_batch`,
|
||||||
|
dataType: 'form',
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
name: 'lines',
|
||||||
|
type: 'textarea',
|
||||||
|
label: '表信息 (flink_job_id alias\\n)',
|
||||||
|
clearable: true,
|
||||||
|
minRows: 5,
|
||||||
|
maxRows: 5,
|
||||||
|
className: 'no-resize',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
title: '停止所有压缩任务',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/schedule/stop_all`,
|
||||||
|
data: {
|
||||||
|
flink_job_id: '${flinkJobId|default:undefined}',
|
||||||
|
alias: '${alias|default:undefined}',
|
||||||
|
disable_meta: '${disableMeta|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...formReloadFlinkJobIdTextInputAndAliasTextInput('163e043e-8cee-41fd-b5a4-0442ac682aec'),
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
...flinkJobIdTextInput('163e043e-8cee-41fd-b5a4-0442ac682aec', true),
|
||||||
|
columnRatio: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...aliasTextInput('163e043e-8cee-41fd-b5a4-0442ac682aec', true),
|
||||||
|
columnRatio: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disableMeta',
|
||||||
|
type: 'checkbox',
|
||||||
|
label: '是否禁用表',
|
||||||
|
option: '表status设为n',
|
||||||
|
columnRatio: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tool
|
||||||
185
service-web-client/src/pages/Version.tsx
Normal file
185
service-web-client/src/pages/Version.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import {
|
||||||
|
aliasTextInput,
|
||||||
|
amisRender,
|
||||||
|
commonInfo,
|
||||||
|
crudCommonOptions,
|
||||||
|
filterableField,
|
||||||
|
flinkJobDialog,
|
||||||
|
flinkJobIdTextInput,
|
||||||
|
mappingField,
|
||||||
|
paginationCommonOptions,
|
||||||
|
tableMetaDialog,
|
||||||
|
versionUpdateStateMapping,
|
||||||
|
} from '../util/amis.ts'
|
||||||
|
|
||||||
|
function Version() {
|
||||||
|
return (
|
||||||
|
<div className="hudi-version">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/version_update/list`,
|
||||||
|
data: {
|
||||||
|
page: '${page|default:undefined}',
|
||||||
|
count: '${perPage|default:undefined}',
|
||||||
|
order: '${orderBy|default:undefined}',
|
||||||
|
direction: '${orderDir|default:undefined}',
|
||||||
|
search_flink_job_id: '${flinkJobId|default:undefined}',
|
||||||
|
search_alias: '${alias|default:undefined}',
|
||||||
|
search_version: '${version|default:undefined}',
|
||||||
|
filter_schedules: '${updated|default:undefined}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
now: '${DATETOSTR(DATEMODIFY(NOW(), -1, \'days\'), \'YYYYMMDD\')}',
|
||||||
|
},
|
||||||
|
...crudCommonOptions(),
|
||||||
|
interval: 10000,
|
||||||
|
filter: {
|
||||||
|
mode: 'inline',
|
||||||
|
title: '表筛选',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
...flinkJobIdTextInput('c5cac9d3-844a-4d86-b2c5-0c10f2283667'),
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...aliasTextInput('c5cac9d3-844a-4d86-b2c5-0c10f2283667'),
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-date',
|
||||||
|
name: 'version',
|
||||||
|
label: '版本',
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '通过版本搜索',
|
||||||
|
size: 'md',
|
||||||
|
format: 'YYYYMMDD',
|
||||||
|
inputFormat: 'YYYYMMDD',
|
||||||
|
value: '${now}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
level: 'primary',
|
||||||
|
label: '查询',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'reset',
|
||||||
|
label: '重置',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
filterTogglable: true,
|
||||||
|
filterDefaultVisible: true,
|
||||||
|
perPage: 20,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
'filter-toggler',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '共 <span class=\'text-primary font-bold\'>${total|default:0}</span> 个表,其中 <span class=\'text-success font-bold\'>${scheduled|default:0}</span> 个表已跨天,<span class=\'text-danger font-bold\'>${unScheduled|default:0}</span> 个表未跨天',
|
||||||
|
},
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: 'Flink job id',
|
||||||
|
width: 195,
|
||||||
|
fixed: 'left',
|
||||||
|
type: 'wrapper',
|
||||||
|
size: 'none',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '${flinkJobId}',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'dialog',
|
||||||
|
tooltip: '查看详情',
|
||||||
|
dialog: flinkJobDialog(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${flinkJobId}',
|
||||||
|
tooltip: '复制 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '别名',
|
||||||
|
type: 'wrapper',
|
||||||
|
fixed: 'left',
|
||||||
|
size: 'none',
|
||||||
|
className: 'nowrap',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '${tableMeta.alias}',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'dialog',
|
||||||
|
tooltip: '查看详情',
|
||||||
|
dialog: tableMetaDialog(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
level: 'link',
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
size: 'xs',
|
||||||
|
actionType: 'copy',
|
||||||
|
content: '${tableMeta.alias}',
|
||||||
|
tooltip: '复制别名',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
label: '表优先级',
|
||||||
|
align: 'center',
|
||||||
|
width: 75,
|
||||||
|
sortable: true,
|
||||||
|
}, {
|
||||||
|
name: 'version',
|
||||||
|
label: '版本',
|
||||||
|
align: 'center',
|
||||||
|
width: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updated',
|
||||||
|
label: '状态',
|
||||||
|
align: 'center',
|
||||||
|
...mappingField('updated', versionUpdateStateMapping),
|
||||||
|
filterable: filterableField(versionUpdateStateMapping, true),
|
||||||
|
width: 70,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Version
|
||||||
122
service-web-client/src/pages/Yarn.tsx
Normal file
122
service-web-client/src/pages/Yarn.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {useParams} from 'react-router'
|
||||||
|
import {
|
||||||
|
amisRender,
|
||||||
|
commonInfo,
|
||||||
|
crudCommonOptions,
|
||||||
|
paginationCommonOptions,
|
||||||
|
yarnCrudColumns,
|
||||||
|
yarnQueueCrud,
|
||||||
|
} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const Yarn: React.FC = () => {
|
||||||
|
const {clusters, queue, search} = useParams()
|
||||||
|
return (
|
||||||
|
<div className="hudi-yarn bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
id: `${clusters}-yarn-service`,
|
||||||
|
name: `${clusters}-yarn-service`,
|
||||||
|
type: 'service',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold text-xl">集群资源</span>',
|
||||||
|
},
|
||||||
|
yarnQueueCrud(clusters, queue),
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '<span class="font-bold text-xl">集群任务</span>',
|
||||||
|
// className: 'mb-2 block',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: `${commonInfo.baseUrl}/yarn/job_list`,
|
||||||
|
data: {
|
||||||
|
clusters: `${clusters}`,
|
||||||
|
page: '${page|default:undefined}',
|
||||||
|
count: '${perPage|default:undefined}',
|
||||||
|
order: '${orderBy|default:undefined}',
|
||||||
|
direction: '${orderDir|default:undefined}',
|
||||||
|
filter_state: '${state|default:undefined}',
|
||||||
|
filter_final_status: '${finalStatus|default:undefined}',
|
||||||
|
search_id: '${id|default:undefined}',
|
||||||
|
search_name: '${name|default:undefined}',
|
||||||
|
completion: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultParams: {
|
||||||
|
name: search,
|
||||||
|
},
|
||||||
|
...crudCommonOptions(),
|
||||||
|
interval: 10000,
|
||||||
|
filter: {
|
||||||
|
mode: 'inline',
|
||||||
|
title: '任务筛选',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'id',
|
||||||
|
label: 'ID',
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '通过 ID 搜索',
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '通过名称搜索',
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'submit',
|
||||||
|
level: 'primary',
|
||||||
|
label: '查询',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'reset',
|
||||||
|
label: '重置',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
filterTogglable: true,
|
||||||
|
filterDefaultVisible: false,
|
||||||
|
perPage: 20,
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
'filter-toggler',
|
||||||
|
{
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '共 <span class=\'text-primary font-bold\'>${total|default:0}</span> 个任务,其中 <span class=\'text-success font-bold\'>${running|default:0}</span> 个任务运行中,<span class=\'text-danger font-bold\'>${unRunning|default:0}</span> 个任务处于非运行状态',
|
||||||
|
},
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationCommonOptions(),
|
||||||
|
],
|
||||||
|
columns: yarnCrudColumns(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Yarn
|
||||||
19
service-web-client/src/pages/YarnCluster.tsx
Normal file
19
service-web-client/src/pages/YarnCluster.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {amisRender, commonInfo, yarnQueueCrud} from '../util/amis.ts'
|
||||||
|
|
||||||
|
const YarnCluster: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="hudi-yarn-cluster bg-white">
|
||||||
|
{amisRender(
|
||||||
|
{
|
||||||
|
type: 'wrapper',
|
||||||
|
body: [
|
||||||
|
...Object.keys(commonInfo.clusters.compaction).map(name => yarnQueueCrud(name)),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default YarnCluster
|
||||||
2482
service-web-client/src/util/amis.ts
Normal file
2482
service-web-client/src/util/amis.ts
Normal file
File diff suppressed because it is too large
Load Diff
1
service-web-client/src/vite-env.d.ts
vendored
Normal file
1
service-web-client/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
31
service-web-client/tsconfig.json
Normal file
31
service-web-client/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
7
service-web-client/vite.config.ts
Normal file
7
service-web-client/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import {defineConfig} from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react-swc'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user