2577 lines
83 KiB
TypeScript
2577 lines
83 KiB
TypeScript
import {AlertComponent, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
|
||
|
||
import 'amis/lib/themes/antd.css'
|
||
import 'amis/lib/helper.css'
|
||
import 'amis/sdk/iconfont.css'
|
||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||
import axios from 'axios'
|
||
import {isEqual} from 'licia'
|
||
|
||
export const commonInfo = {
|
||
debug: isEqual(import.meta.env.MODE, 'development'),
|
||
baseUrl: 'http://132.126.207.130:35690/hudi_services/service_web',
|
||
baseAiUrl: 'http://132.126.207.130:35690/hudi_services/service_ai_web',
|
||
// baseAiUrl: 'http://localhost:8080',
|
||
authorizationHeaders: {
|
||
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
clusters: {
|
||
// hudi同步运行集群和yarn队列名称
|
||
sync: {
|
||
'b12': 'default',
|
||
},
|
||
sync_names() {
|
||
return Object.keys(this.sync).join(',')
|
||
},
|
||
// hudi压缩运行集群和yarn队列名称
|
||
compaction: {
|
||
'b12': 'default',
|
||
'b1': 'datalake',
|
||
'a4': 'ten_iap.datalake',
|
||
},
|
||
compaction_names() {
|
||
return Object.keys(this.compaction).join(',')
|
||
},
|
||
},
|
||
loki: {
|
||
// grafana链接,用于直接打开grafana日志查看
|
||
grafanaUrl: 'http://132.126.207.125:35700',
|
||
// grafana对应hudi使用的loki配置的datasource id
|
||
hudi: {
|
||
datasource: 'f648174e-7593-45cf-8fe8-8f8d5cf0fdde',
|
||
},
|
||
// grafana对应服务使用的loki配置的datasource id
|
||
service: {
|
||
datasource: 'b6fee51c-facd-4261-a0eb-8c69a975fba3',
|
||
},
|
||
},
|
||
}
|
||
|
||
const __ = makeTranslator('zh')
|
||
|
||
const responseAdaptor = () => (response: any) => {
|
||
let payload = response.data || {} // blob 下可能会返回内容为空?
|
||
if (payload.hasOwnProperty('errno')) {
|
||
payload.status = payload.errno
|
||
payload.msg = payload.errmsg
|
||
} else if (payload.hasOwnProperty('no')) {
|
||
payload.status = payload.no
|
||
payload.msg = payload.error
|
||
}
|
||
return {
|
||
...response,
|
||
data: payload,
|
||
}
|
||
}
|
||
|
||
export const amisRender = (schema: Schema, data: Record<any, any> = {}) => {
|
||
const theme = 'antd'
|
||
const locale = 'zh-CN'
|
||
return (
|
||
<>
|
||
<ToastComponent
|
||
theme={theme}
|
||
key="toast"
|
||
position={'top-right'}
|
||
locale={locale}
|
||
/>
|
||
<AlertComponent theme={theme} key="alert" locale={locale}/>
|
||
{render(
|
||
schema,
|
||
{
|
||
data: data,
|
||
theme: theme,
|
||
},
|
||
{
|
||
enableAMISDebug: commonInfo.debug,
|
||
fetcher: async (api: any) => {
|
||
let {url, method, data, responseType, config, headers} = api
|
||
config = config || {}
|
||
config.url = url
|
||
config.withCredentials = true
|
||
responseType && (config.responseType = responseType)
|
||
|
||
if (config.cancelExecutor) {
|
||
config.cancelToken = new (axios as any).CancelToken(
|
||
config.cancelExecutor,
|
||
)
|
||
}
|
||
|
||
config.headers = headers || {}
|
||
config.method = method
|
||
config.data = data
|
||
|
||
if (method === 'get' && data) {
|
||
config.params = data
|
||
} else if (data && data instanceof FormData) {
|
||
// config.headers['Content-Type'] = 'multipart/form-data';
|
||
} else if (
|
||
data &&
|
||
typeof data !== 'string' &&
|
||
!(data instanceof Blob) &&
|
||
!(data instanceof ArrayBuffer)
|
||
) {
|
||
data = JSON.stringify(data)
|
||
config.headers['Content-Type'] = 'application/json'
|
||
}
|
||
|
||
// 支持返回各种报错信息
|
||
config.validateStatus = function () {
|
||
return true
|
||
}
|
||
|
||
let response = await axios(config)
|
||
response = await attachmentAdpator(response, __, api)
|
||
response = responseAdaptor()(response)
|
||
|
||
if (response.status >= 400) {
|
||
if (response.data) {
|
||
// 主要用于 raw: 模式下,后端自己校验登录,
|
||
if (
|
||
response.status === 401 &&
|
||
response.data.location &&
|
||
response.data.location.startsWith('http')
|
||
) {
|
||
location.href = response.data.location.replace(
|
||
'{{redirect}}',
|
||
encodeURIComponent(location.href),
|
||
)
|
||
return new Promise(() => {
|
||
})
|
||
} else if (response.data.msg) {
|
||
throw new Error(response.data.msg)
|
||
} else {
|
||
throw new Error(
|
||
'System.requestError' + JSON.stringify(response.data, null, 2),
|
||
)
|
||
}
|
||
} else {
|
||
throw new Error(
|
||
`${'System.requestErrorStatus'} ${response.status}`,
|
||
)
|
||
}
|
||
}
|
||
|
||
return response
|
||
},
|
||
isCancel: (value: any) => (axios as any).isCancel(value),
|
||
},
|
||
)}
|
||
</>
|
||
)
|
||
}
|
||
|
||
function generateLokiPanel(queries: Array<any>) {
|
||
return {
|
||
LWF: {
|
||
queries: [
|
||
...queries.map(item => {
|
||
let name = item['name']
|
||
let datasource = item['datasource']
|
||
let queryMap = item['queryMap']
|
||
let query = Object.keys(queryMap)
|
||
.sort()
|
||
.map(key => `${key}="${queryMap[key]}"`)
|
||
let match = '\\d{4}-(?P<time>\\d{2}-\\d{2}\\s*\\d{2}:\\d{2}:\\d{2}).+#@#\\s*(?P<content>[\\w\\W]+)'
|
||
let format = '{{.time}} [{{.host}}] [{{.level}}] [{{.app}}] {{.content}}'
|
||
// language=TEXT
|
||
let expression = `{${query.join(',')}}\n| regexp "${match.replaceAll('\\', '\\\\')}"\n| line_format \`${format}\``
|
||
return {
|
||
refId: name,
|
||
expr: expression,
|
||
queryType: 'range',
|
||
datasource: {
|
||
type: 'loki',
|
||
uid: datasource,
|
||
},
|
||
editorMode: 'code',
|
||
}
|
||
}),
|
||
],
|
||
range: {
|
||
from: 'now-1h',
|
||
to: 'now',
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
function generateQuery(name: string, datasource: string, queryMap: any) {
|
||
return {
|
||
name: name,
|
||
datasource: datasource,
|
||
queryMap: queryMap,
|
||
}
|
||
}
|
||
|
||
function generateLokiUrl(baseUrl: string, queries: Array<any>) {
|
||
return `${baseUrl}/explore?panes=${encodeURIComponent(JSON.stringify(generateLokiPanel(queries)))}&schemaVersion=1&orgId=1`
|
||
}
|
||
|
||
function targetHudiSyncLokiUrlByAlias(flinkJobId: number) {
|
||
return generateLokiUrl(
|
||
commonInfo.loki.grafanaUrl,
|
||
[
|
||
generateQuery(
|
||
'Hudi 运行日志',
|
||
commonInfo.loki.hudi.datasource,
|
||
{'flink_job_id': flinkJobId},
|
||
),
|
||
],
|
||
)
|
||
}
|
||
|
||
function targetHudiCompactionLokiUrlByAlias(alias: string) {
|
||
return generateLokiUrl(
|
||
commonInfo.loki.grafanaUrl,
|
||
[
|
||
generateQuery(
|
||
'Hudi 运行日志',
|
||
commonInfo.loki.hudi.datasource,
|
||
{'alias': alias},
|
||
),
|
||
],
|
||
)
|
||
}
|
||
|
||
function targetYarnApplicationLokiUrlByAppId(applicationId: string) {
|
||
return generateLokiUrl(
|
||
commonInfo.loki.grafanaUrl,
|
||
[
|
||
generateQuery(
|
||
'Hudi 运行日志',
|
||
commonInfo.loki.hudi.datasource,
|
||
{'app_id': applicationId},
|
||
),
|
||
],
|
||
)
|
||
}
|
||
|
||
export function serviceLogByAppName(name: string) {
|
||
return generateLokiUrl(
|
||
commonInfo.loki.grafanaUrl,
|
||
[
|
||
generateQuery(
|
||
'Service 运行日志',
|
||
commonInfo.loki.service.datasource,
|
||
{'app': name},
|
||
),
|
||
],
|
||
)
|
||
}
|
||
|
||
export function serviceLogByAppNameAndHost(name: string, host: string) {
|
||
return generateLokiUrl(
|
||
commonInfo.loki.grafanaUrl,
|
||
[
|
||
generateQuery(
|
||
'Service 运行日志',
|
||
commonInfo.loki.service.datasource,
|
||
{'app': name, 'host': host},
|
||
),
|
||
],
|
||
)
|
||
}
|
||
|
||
export function horizontalFormOptions() {
|
||
return {
|
||
mode: 'horizontal',
|
||
horizontal: {
|
||
leftFixed: 'sm',
|
||
},
|
||
}
|
||
}
|
||
|
||
export function crudCommonOptions() {
|
||
return {
|
||
affixHeader: false,
|
||
stopAutoRefreshWhenModalIsOpen: true,
|
||
resizable: false,
|
||
syncLocation: false,
|
||
silentPolling: true,
|
||
columnsTogglable: false,
|
||
}
|
||
}
|
||
|
||
export function readOnlyDialogOptions() {
|
||
return {
|
||
actions: [],
|
||
showCloseButton: false,
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
disabled: true,
|
||
}
|
||
}
|
||
|
||
export function paginationCommonOptions(perPage = true, maxButtons = 5) {
|
||
let option = {
|
||
type: 'pagination',
|
||
layout: [
|
||
'pager',
|
||
],
|
||
maxButtons: maxButtons,
|
||
showPageInput: false,
|
||
perPageAvailable: [10, 15, 20, 50, 100, 200],
|
||
}
|
||
if (perPage) {
|
||
option.layout.push('perPage')
|
||
}
|
||
return option
|
||
}
|
||
|
||
export function paginationTemplate(perPage = 20, maxButtons = 5, extraHeaders: Schema[] = [], extraFooters: Schema[] = []) {
|
||
return {
|
||
perPage: perPage,
|
||
headerToolbar: [
|
||
'reload',
|
||
paginationCommonOptions(true, maxButtons),
|
||
...extraHeaders,
|
||
],
|
||
footerToolbar: [
|
||
'statistics',
|
||
paginationCommonOptions(true, maxButtons),
|
||
...extraFooters,
|
||
],
|
||
}
|
||
}
|
||
|
||
export function timeAndFrom(field: string, fromNow: string, emptyText = '未停止', showSource = true) {
|
||
let tpl = '${IF(' + field + ' === 0, \'(' + emptyText + ')\', CONCATENATE(\'<span class="font-bold">\',' + fromNow + ',\'</span>\'))}'
|
||
if (showSource) {
|
||
return {
|
||
align: 'center',
|
||
width: 80,
|
||
type: 'tooltip-wrapper',
|
||
content: '${DATETOSTR(DATE(' + field + '))}',
|
||
inline: true,
|
||
className: 'mr-2',
|
||
disabledOn: `\${${field} === 0}`,
|
||
body: {
|
||
type: 'tpl',
|
||
tpl: tpl,
|
||
},
|
||
}
|
||
} else {
|
||
return {
|
||
align: 'center',
|
||
width: 140,
|
||
type: 'tpl',
|
||
tpl: '${DATETOSTR(DATE(' + field + '))}',
|
||
}
|
||
}
|
||
}
|
||
|
||
export function applicationLogDialog() {
|
||
return {
|
||
type: 'action',
|
||
level: 'link',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '应用日志',
|
||
size: 'xl',
|
||
actions: [],
|
||
body: [
|
||
{
|
||
type: 'service',
|
||
api: {
|
||
method: 'GET',
|
||
url: `${commonInfo.baseUrl}/log/query_application_log`,
|
||
data: {
|
||
application_id: '${id}',
|
||
},
|
||
},
|
||
body: [
|
||
{
|
||
disabled: true,
|
||
type: 'editor',
|
||
name: 'detail',
|
||
label: '应用日志',
|
||
size: 'xxl',
|
||
placeholder: '没有内容',
|
||
options: {
|
||
wordWrap: 'on',
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
}
|
||
}
|
||
|
||
export function yarnQueueCrud(clusters?: string, queueNames?: string) {
|
||
return {
|
||
type: 'crud',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/yarn/queue_list`,
|
||
data: {
|
||
clusters: '${cluster|default:undefined}',
|
||
names: '${queueName|default:undefined}',
|
||
},
|
||
},
|
||
affixHeader: false,
|
||
defaultParams: {
|
||
...(clusters ? {cluster: clusters} : {}),
|
||
...(queueNames ? {queueName: queueNames} : {}),
|
||
},
|
||
interval: 10000,
|
||
syncLocation: false,
|
||
silentPolling: true,
|
||
headerToolbar: [
|
||
'reload',
|
||
],
|
||
columns: [
|
||
{
|
||
name: 'queueName',
|
||
label: '队列名称',
|
||
width: 130,
|
||
type: 'tooltip-wrapper',
|
||
body: '${TRUNCATE(queueName, 20)}',
|
||
content: '${queueName}',
|
||
inline: true,
|
||
},
|
||
{
|
||
label: '当前容量',
|
||
type: 'progress',
|
||
value: '${ROUND((absoluteUsedCapacity * 100 / absoluteMaxCapacity), 0)}',
|
||
stripe: true,
|
||
animate: true,
|
||
showLabel: false,
|
||
map: [
|
||
{
|
||
value: 30,
|
||
color: '#28a745',
|
||
}, {
|
||
value: 90,
|
||
color: '#007bff',
|
||
},
|
||
{
|
||
value: 100,
|
||
color: '#dc3545',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
label: '进度',
|
||
width: 35,
|
||
align: 'center',
|
||
type: 'tpl',
|
||
tpl: '${ROUND((absoluteUsedCapacity * 100 / absoluteMaxCapacity), 0)}%',
|
||
},
|
||
{
|
||
type: 'operation',
|
||
label: '操作',
|
||
width: 100,
|
||
fixed: 'right',
|
||
buttons: [
|
||
{
|
||
label: '详情',
|
||
type: 'button',
|
||
level: 'link',
|
||
tooltip: '查看队列详情',
|
||
visibleOn: '${!root}',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
size: 'md',
|
||
close: true,
|
||
title: '队列详情',
|
||
body: {
|
||
type: 'property',
|
||
items: [
|
||
{label: 'CPU', content: '${resourcesUsed.vCores}'},
|
||
// 有空看看这个值的单位
|
||
{label: '内存', content: '${resourcesUsed.memory}', span: 2},
|
||
{label: '容量(%)', content: '${capacity}'},
|
||
{label: '最大容量(%)', content: '${maxCapacity}'},
|
||
{label: '已用容量(%)', content: '${usedCapacity}'},
|
||
{label: '绝对容量(%)', content: '${absoluteCapacity}'},
|
||
{label: '绝对最大容量(%)', content: '${absoluteMaxCapacity}'},
|
||
{label: '绝对已用容量(%)', content: '${absoluteUsedCapacity}'},
|
||
{label: '应用数量', content: '${numApplications}', span: 3},
|
||
{label: '最大应用数量', content: '${maxApplications}'},
|
||
{label: '活跃应用数量', content: '${numActiveApplications}'},
|
||
{label: '等待应用数量', content: '${numPendingApplications}'},
|
||
{label: '容器数量', content: '${numContainers}', span: 3},
|
||
{label: '已分配容器数量', content: '${numApplications}'},
|
||
{label: '预留容器数量数量', content: '${numApplications}'},
|
||
{label: '等待容器数量', content: '${numApplications}'},
|
||
],
|
||
},
|
||
actions: [],
|
||
},
|
||
},
|
||
{
|
||
visibleOn: '${webUrl}',
|
||
label: '管理页面',
|
||
type: 'action',
|
||
level: 'link',
|
||
tooltip: '打开管理页面',
|
||
actionType: 'url',
|
||
url: '${webUrl}',
|
||
blank: true,
|
||
},
|
||
],
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function yarnCrudColumns() {
|
||
return [
|
||
{
|
||
label: '名称',
|
||
className: 'nowrap',
|
||
type: 'tpl',
|
||
tpl: '${IF(syncApplication, \'<span class="rounded-xl label label-primary">S</span>\', IF(compactionApplication, \'<span class="rounded-xl label label-primary">C</span>\', IF(taskApplication, \'<span class="rounded-xl label label-primary">T</span>\', \'\')))}${IF(hudiApplication || taskApplication, \'<span class="mx-2"/>\', \'\')}${IF(syncApplication, flinkJobName, IF(compactionApplication, alias, IF(taskApplication, taskName, name)))}',
|
||
},
|
||
{
|
||
name: 'cluster',
|
||
label: '集群',
|
||
width: 65,
|
||
align: 'center',
|
||
type: 'tpl',
|
||
tpl: '<span class="label label-info">${cluster}</span>',
|
||
},
|
||
{
|
||
label: '用户',
|
||
width: 80,
|
||
type: 'tooltip-wrapper',
|
||
body: '${TRUNCATE(user, 8)}',
|
||
content: '${user}',
|
||
align: 'center',
|
||
},
|
||
{
|
||
name: 'state',
|
||
label: '运行状态',
|
||
width: 70,
|
||
align: 'center',
|
||
type: 'mapping',
|
||
canAccessSuperData: false,
|
||
map: {
|
||
'NEW': '<span class=\'label bg-pink-300\'>创建中</span>',
|
||
'NEW_SAVING': '<span class=\'label bg-purple-300\'>已创建</span>',
|
||
'SUBMITTED': '<span class=\'label bg-indigo-300\'>已提交</span>',
|
||
'ACCEPTED': '<span class=\'label bg-cyan-300\'>调度中</span>',
|
||
'RUNNING': '<span class=\'label label-success\'>运行中</span>',
|
||
'FINISHED': '<span class=\'label label-default\'>已结束</span>',
|
||
'FAILED': '<span class=\'label label-danger\'>已失败</span>',
|
||
'KILLED': '<span class=\'label label-warning\'>被停止</span>',
|
||
'*': '<span class=\'label bg-gray-300\'>${state}</span>',
|
||
},
|
||
filterable: {
|
||
multiple: true,
|
||
options: [
|
||
{label: '创建中', value: 'NEW'},
|
||
{label: '已创建', value: 'NEW_SAVING'},
|
||
{label: '已提交', value: 'SUBMITTED'},
|
||
{label: '调度中', value: 'ACCEPTED'},
|
||
{label: '运行中', value: 'RUNNING'},
|
||
{label: '已结束', value: 'FINISHED'},
|
||
{label: '已失败', value: 'FAILED'},
|
||
{label: '被停止', value: 'KILLED'},
|
||
],
|
||
},
|
||
},
|
||
{
|
||
name: 'finalStatus',
|
||
label: '最终状态',
|
||
width: 70,
|
||
align: 'center',
|
||
type: 'mapping',
|
||
canAccessSuperData: false,
|
||
map: {
|
||
'UNDEFINED': '<span class=\'label label-info\'>运行</span>',
|
||
'SUCCEEDED': '<span class=\'label label-success\'>成功</span>',
|
||
'FAILED': '<span class=\'label label-danger\'>失败</span>',
|
||
'KILLED': '<span class=\'label label-warning\'>终止</span>',
|
||
'ENDED': '<span class=\'label label-default\'>结束</span>',
|
||
'*': '<span class=\'label bg-gray-300\'>${state}</span>',
|
||
},
|
||
filterable: {
|
||
multiple: true,
|
||
options: [
|
||
{label: '运行', value: 'UNDEFINED'},
|
||
{label: '成功', value: 'SUCCEEDED'},
|
||
{label: '失败', value: 'FAILED'},
|
||
{label: '终止', value: 'KILLED'},
|
||
{label: '结束', value: 'ENDED'},
|
||
],
|
||
},
|
||
},
|
||
{
|
||
name: 'startedTime',
|
||
label: '启动时间',
|
||
...timeAndFrom('startedTime', 'startTimeFromNow'),
|
||
sortable: true,
|
||
canAccessSuperData: false,
|
||
},
|
||
{
|
||
name: 'finishedTime',
|
||
label: '停止时间',
|
||
...timeAndFrom('finishedTime', 'finishTimeFromNow'),
|
||
sortable: true,
|
||
align: 'center',
|
||
canAccessSuperData: false,
|
||
},
|
||
{
|
||
type: 'operation',
|
||
width: 160,
|
||
label: '操作',
|
||
fixed: 'right',
|
||
className: 'nowrap',
|
||
buttons: [
|
||
/*{
|
||
disabled: true,
|
||
label: "停止",
|
||
type: "button",
|
||
level: "link",
|
||
tooltip: '从队列中移除该任务',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
size: 'sm',
|
||
close: true,
|
||
title: '停止确认',
|
||
body: '确认停止任务: <br>${name}<br> ?',
|
||
actions: [
|
||
{
|
||
type: 'button',
|
||
label: '取消',
|
||
actionType: 'cancel'
|
||
},
|
||
{
|
||
type: 'button',
|
||
label: '确认移除',
|
||
level: 'danger',
|
||
actionType: 'ajax',
|
||
api: '',
|
||
primary: true,
|
||
message: {
|
||
success: '操作成功',
|
||
failed: '操作失败'
|
||
}
|
||
}
|
||
]
|
||
}
|
||
},*/
|
||
{
|
||
visibleOn: 'flinkJobId',
|
||
label: 'FID',
|
||
type: 'action',
|
||
actionType: 'copy',
|
||
content: '${flinkJobId}',
|
||
level: 'link',
|
||
tooltip: '${flinkJobId}',
|
||
},
|
||
{
|
||
visibleOn: 'alias',
|
||
label: 'ALIAS',
|
||
type: 'action',
|
||
actionType: 'copy',
|
||
content: '${alias}',
|
||
level: 'link',
|
||
tooltip: '${alias}',
|
||
},
|
||
{
|
||
disabledOn: '${finalStatus != \'SUCCEEDED\' || state != \'FINISHED\'}',
|
||
disabledTip: '无结果',
|
||
visibleOn: 'taskId',
|
||
label: '结果',
|
||
level: 'link',
|
||
type: 'action',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '结果',
|
||
...readOnlyDialogOptions(),
|
||
size: 'xl',
|
||
body: [
|
||
{
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/task/results`,
|
||
data: {
|
||
task_id: '${taskId|default:undefined}',
|
||
},
|
||
},
|
||
body: {
|
||
type: 'editor',
|
||
disabled: true,
|
||
name: 'text',
|
||
options: {
|
||
wordWrap: 'on',
|
||
fontFamily: 'monospace',
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
},
|
||
{
|
||
label: 'ID',
|
||
type: 'action',
|
||
actionType: 'copy',
|
||
content: '${id}',
|
||
level: 'link',
|
||
tooltip: '${id}',
|
||
},
|
||
{
|
||
disabledOn: '${trackingUrl == null}',
|
||
disabledTip: '无应用页面',
|
||
label: '页面',
|
||
type: 'action',
|
||
level: 'link',
|
||
tooltip: '打开应用页面: ${trackingUrl}',
|
||
actionType: 'url',
|
||
url: '${trackingUrl}',
|
||
blank: true,
|
||
},
|
||
{
|
||
disabledOn: '${id == null || !(hudiApplication | taskApplication)}',
|
||
disabledTip: '找不到id或非hudi服务管理的任务',
|
||
label: '日志',
|
||
type: 'action',
|
||
level: 'link',
|
||
tooltip: '打开Grafana日志',
|
||
onEvent: {
|
||
click: {
|
||
actions: [
|
||
{
|
||
actionType: 'custom',
|
||
// @ts-ignore
|
||
script: (context, doAction, event) => {
|
||
let isSyncApplication = context.props.data?.syncApplication ?? false
|
||
let isCompactionApplication = context.props.data?.compactionApplication ?? false
|
||
let flinkJobId = context.props.data?.flinkJobId ?? 0
|
||
let alias = context.props.data?.alias ?? ''
|
||
let appId = context.props.data?.id ?? ''
|
||
let url = ''
|
||
if (isSyncApplication) {
|
||
url = targetHudiSyncLokiUrlByAlias(flinkJobId)
|
||
} else if (isCompactionApplication) {
|
||
url = targetHudiCompactionLokiUrlByAlias(alias)
|
||
} else {
|
||
url = targetYarnApplicationLokiUrlByAppId(appId)
|
||
}
|
||
window.open(url, '_blank')
|
||
},
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
]
|
||
}
|
||
|
||
export function simpleYarnDialog(cluster: string, title: string, filterField: string) {
|
||
return {
|
||
title: title,
|
||
actions: [],
|
||
data: {
|
||
base: '${base}',
|
||
name: `\${${filterField}}`,
|
||
flinkJob: '${flinkJob}',
|
||
tableMeta: '${tableMeta}',
|
||
},
|
||
size: 'xl',
|
||
body: [
|
||
{
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/yarn/job_current`,
|
||
data: {
|
||
clusters: `${cluster}`,
|
||
name: '${name}',
|
||
},
|
||
},
|
||
silentPolling: false,
|
||
body: [
|
||
{
|
||
type: 'wrapper',
|
||
size: 'none',
|
||
visibleOn: '${hasCurrent}',
|
||
body: [
|
||
{
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/flink/overview`,
|
||
data: {
|
||
url: '${current.trackingUrl}',
|
||
},
|
||
},
|
||
silentPolling: false,
|
||
body: [
|
||
{
|
||
type: 'property',
|
||
title: 'Flink 基本信息',
|
||
column: 4,
|
||
items: [
|
||
{label: 'Flink 版本', content: '${flinkVersion}'},
|
||
{label: 'Flink 小版本', content: '${flinkCommit}', span: 3},
|
||
{label: '运行中', content: '${jobsRunning}'},
|
||
{label: '已结束', content: '${jobsFinished}'},
|
||
{label: '已失败', content: '${jobsFailed}'},
|
||
{label: '被取消', content: '${jobsCanceled}'},
|
||
{
|
||
label: 'Slot (可用/总数)',
|
||
content: '${slotsAvailable}/${slotsTotal}',
|
||
span: 4,
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/flink/jobs`,
|
||
data: {
|
||
url: '${current.trackingUrl}',
|
||
schema: '${tableMeta.schema}',
|
||
table: '${tableMeta.table}',
|
||
mode: '${flinkJob.runMode}',
|
||
},
|
||
},
|
||
silentPolling: false,
|
||
body: [
|
||
{
|
||
type: 'table',
|
||
title: '任务详情',
|
||
source: '${items}',
|
||
affixHeader: false,
|
||
columns: [
|
||
{
|
||
name: 'name',
|
||
label: '名称',
|
||
width: 2000,
|
||
},
|
||
{
|
||
label: 'Checkpoint',
|
||
width: 60,
|
||
align: 'center',
|
||
fixed: 'right',
|
||
type: 'tpl',
|
||
tpl: '${IF(checkpointMetrics, checkpointMetrics.complete + \'/\' + checkpointMetrics.inProgress +\'/\' + checkpointMetrics.failed, \'-\')}',
|
||
},
|
||
{
|
||
name: 'metrics.readRecords',
|
||
label: '读记录数',
|
||
width: 60,
|
||
align: 'center',
|
||
fixed: 'right',
|
||
},
|
||
{
|
||
name: 'metrics.writeRecords',
|
||
label: '写记录数',
|
||
width: 60,
|
||
align: 'center',
|
||
fixed: 'right',
|
||
},
|
||
{
|
||
label: '操作',
|
||
width: 60,
|
||
align: 'center',
|
||
fixed: 'right',
|
||
type: 'wrapper',
|
||
size: 'none',
|
||
body: [
|
||
{
|
||
disabled: true,
|
||
type: 'button',
|
||
label: '详情',
|
||
level: 'link',
|
||
size: 'xs',
|
||
actionType: 'url',
|
||
blank: true,
|
||
url: '${page}',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
type: 'tpl',
|
||
tpl: '没有正在运行的任务',
|
||
visibleOn: '${!hasCurrent}',
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'crud',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/yarn/job_list`,
|
||
data: {
|
||
clusters: `${cluster}`,
|
||
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}',
|
||
precise: true,
|
||
},
|
||
},
|
||
affixHeader: false,
|
||
interval: 10000,
|
||
syncLocation: false,
|
||
silentPolling: true,
|
||
resizable: false,
|
||
perPage: 10,
|
||
headerToolbar: [
|
||
'reload',
|
||
paginationCommonOptions(),
|
||
],
|
||
footerToolbar: [],
|
||
columns: yarnCrudColumns(),
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function hdfsDialog(hdfsField: string) {
|
||
return {
|
||
title: {
|
||
type: 'tpl',
|
||
tpl: `\${${hdfsField}}`,
|
||
},
|
||
...readOnlyDialogOptions(),
|
||
size: 'xl',
|
||
body: {
|
||
type: 'crud',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/hdfs_list`,
|
||
data: {
|
||
root: `\${${hdfsField}|default:undefined}`,
|
||
},
|
||
},
|
||
deferApi: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/hdfs_list_children`,
|
||
data: {
|
||
root: '${path|default:undefined}',
|
||
},
|
||
},
|
||
...crudCommonOptions(),
|
||
perPage: 10,
|
||
headerToolbar: [
|
||
'reload',
|
||
paginationCommonOptions(undefined, 10),
|
||
],
|
||
footerToolbar: [
|
||
paginationCommonOptions(undefined, 10),
|
||
],
|
||
columns: [
|
||
{
|
||
name: 'name',
|
||
label: '文件名',
|
||
className: 'font-mono',
|
||
},
|
||
{
|
||
name: 'group',
|
||
label: '组',
|
||
width: 70,
|
||
},
|
||
{
|
||
name: 'owner',
|
||
label: '用户',
|
||
width: 70,
|
||
},
|
||
{
|
||
label: '大小',
|
||
width: 90,
|
||
type: 'tpl',
|
||
tpl: '${sizeText}',
|
||
},
|
||
{
|
||
label: '修改时间',
|
||
width: 140,
|
||
type: 'tpl',
|
||
tpl: '${DATETOSTR(DATE(modifyTime), \'YYYY-MM-DD HH:mm:ss\')}',
|
||
},
|
||
{
|
||
type: 'operation',
|
||
label: '操作',
|
||
width: 160,
|
||
fixed: 'right',
|
||
buttons: [
|
||
{
|
||
label: '完整路径',
|
||
type: 'action',
|
||
level: 'link',
|
||
size: 'xs',
|
||
actionType: 'copy',
|
||
content: '${path}',
|
||
tooltip: '复制 ${path}',
|
||
},
|
||
{
|
||
disabledOn: 'folder || size > 1048576',
|
||
label: '查看',
|
||
type: 'action',
|
||
level: 'link',
|
||
size: 'xs',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: {
|
||
type: 'tpl',
|
||
tpl: '文件内容:${path}',
|
||
},
|
||
size: 'md',
|
||
...readOnlyDialogOptions(),
|
||
body: {
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/hdfs_read`,
|
||
data: {
|
||
root: '${path|default:undefined}',
|
||
},
|
||
},
|
||
body: {
|
||
type: 'textarea',
|
||
name: 'text',
|
||
readOnly: true,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
disabledOn: 'folder',
|
||
label: '下载',
|
||
type: 'action',
|
||
level: 'link',
|
||
size: 'xs',
|
||
onEvent: {
|
||
click: {
|
||
actions: [
|
||
{
|
||
actionType: 'custom',
|
||
// @ts-ignore
|
||
script: (context, action, event) => {
|
||
let downloadUrl = `${event.data.base}/hudi/hdfs_download?root=${encodeURI(event.data.path)}`
|
||
window.open(downloadUrl, '_blank')
|
||
},
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
}
|
||
}
|
||
|
||
export function copyField(field: string, tips = '复制', ignoreLength = 0) {
|
||
let tpl = ignoreLength === 0 ? `\${${field}}` : `\${TRUNCATE(${field}, ${ignoreLength})}`
|
||
return {
|
||
type: 'wrapper',
|
||
size: 'none',
|
||
body: [
|
||
{
|
||
type: 'tpl',
|
||
className: 'mr-1',
|
||
tpl: tpl,
|
||
},
|
||
{
|
||
type: 'action',
|
||
level: 'link',
|
||
label: '',
|
||
icon: 'fa fa-copy',
|
||
size: 'xs',
|
||
actionType: 'copy',
|
||
content: `\$${field}`,
|
||
tooltip: `${tips}`,
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function flinkJobProperty(id: string, name: string, runMode: string, tags: string) {
|
||
return {
|
||
type: 'property',
|
||
title: 'Flink Job 配置',
|
||
items: [
|
||
{label: 'ID', content: copyField(id, '复制 ID')},
|
||
{label: '任务名称', content: copyField(name, '复制任务名')},
|
||
{
|
||
label: '任务模式',
|
||
content: {
|
||
...mappingField(`${runMode}`, runModeMapping),
|
||
},
|
||
},
|
||
{
|
||
label: '标签',
|
||
content: {
|
||
type: 'each',
|
||
source: `\${SPLIT(${tags}, ",")}`,
|
||
items: mappingField('item', tagsMapping),
|
||
},
|
||
span: 3,
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function runMetaProperty(runMode: string) {
|
||
return {
|
||
type: 'property',
|
||
title: `${runMode} 运行时信息`,
|
||
items: [
|
||
{label: '运行集群', content: `\${${runMode}Runtime.cluster}`},
|
||
{label: '运行主机', content: copyField(`${runMode}Runtime.host`)},
|
||
{label: '进程', content: copyField(`${runMode}Runtime.jvmPid`)},
|
||
{label: '任务 ID', content: copyField(`${runMode}Runtime.applicationId`), span: 2},
|
||
{label: 'Jar 版本', content: `\${${runMode}Runtime.executorVersion}`},
|
||
{label: '任务名称', content: `\${${runMode}Runtime.flinkJobName}`},
|
||
{label: '容器 ID', content: `\${${runMode}Runtime.containerId}`, span: 2},
|
||
{label: '容器路径', content: copyField(`${runMode}Runtime.containerPath`, undefined, 120), span: 3},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function statisticsProperty(title: string, statistic: string) {
|
||
return {
|
||
type: 'property',
|
||
title: title,
|
||
column: 3,
|
||
items: [
|
||
{label: '扫描总时间', content: `\${${statistic}.totalScanTime}`},
|
||
{label: '压缩日志文件总数', content: `\${${statistic}.totalLogFilesCompacted}`},
|
||
{label: '压缩日志文件大小', content: `\${${statistic}.totalLogFilesSize}`},
|
||
{label: '删除记录数', content: `\${${statistic}.totalRecordsDeleted}`},
|
||
{label: '更新记录数', content: `\${${statistic}.totalRecordsUpdated}`},
|
||
{label: '压缩日志数', content: `\${${statistic}.totalRecordsCompacted}`},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function flinkJobDialog() {
|
||
return {
|
||
title: 'Flink job 详情',
|
||
actions: [],
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
showCloseButton: false,
|
||
size: 'md',
|
||
body: [
|
||
flinkJobProperty('flinkJobId', 'flinkJob.name', 'flinkJob.runMode', 'flinkJob.tags'),
|
||
{type: 'divider'},
|
||
{
|
||
type: 'action',
|
||
label: '打开同步详情',
|
||
actionType: 'dialog',
|
||
dialog: simpleYarnDialog(commonInfo.clusters.sync_names(), '同步详情', 'syncJobName'),
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/table/list_metas`,
|
||
data: {
|
||
flink_job_id: '${flinkJobId}',
|
||
},
|
||
},
|
||
canAccessSuperData: true,
|
||
body: [
|
||
{
|
||
type: 'table',
|
||
title: '包含 Hudi 同步表',
|
||
source: '${items}',
|
||
columns: [
|
||
{
|
||
label: '别名',
|
||
type: 'wrapper',
|
||
size: 'none',
|
||
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: '复制别名',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
label: '并行度',
|
||
name: 'tableMeta.hudi.writeTasks',
|
||
align: 'center',
|
||
},
|
||
{
|
||
label: 'Bucket 数量',
|
||
name: 'tableMeta.hudi.bucketIndexNumber',
|
||
align: 'center',
|
||
},
|
||
{
|
||
label: '写并行度',
|
||
name: 'tableMeta.hudi.writeTasks',
|
||
align: 'center',
|
||
},
|
||
{
|
||
label: '压缩并行度',
|
||
name: 'tableMeta.hudi.compactionTasks',
|
||
align: 'center',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function timelineColumns() {
|
||
return [
|
||
{
|
||
name: 'timestamp',
|
||
label: '时间点',
|
||
width: 150,
|
||
sortable: true,
|
||
},
|
||
{
|
||
name: 'action',
|
||
label: '类型',
|
||
width: 100,
|
||
...mappingField('action', hudiTimelineActionMapping),
|
||
filterable: filterableField(hudiTimelineActionMapping, false),
|
||
},
|
||
{
|
||
name: 'state',
|
||
label: ' 状态',
|
||
width: 80,
|
||
align: 'center',
|
||
...mappingField('state', hudiTimelineStateMapping),
|
||
filterable: filterableField(hudiTimelineStateMapping, true),
|
||
},
|
||
{
|
||
name: 'fileTime',
|
||
label: ' 文件时间',
|
||
width: 150,
|
||
align: 'center',
|
||
type: 'tpl',
|
||
tpl: '${DATETOSTR(DATE(fileTime), \'YYYY-MM-DD HH:mm:ss\')}',
|
||
},
|
||
{
|
||
name: 'fileName',
|
||
label: '文件名',
|
||
type: 'wrapper',
|
||
size: 'none',
|
||
className: 'nowrap',
|
||
body: [
|
||
{
|
||
type: 'tpl',
|
||
tpl: '${fileName}',
|
||
},
|
||
{
|
||
visibleOn: 'action === \'compaction\'',
|
||
type: 'action',
|
||
icon: 'fa fa-eye ml-1',
|
||
level: 'link',
|
||
tooltip: '查看压缩计划',
|
||
size: 'xs',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '压缩计划详情',
|
||
actions: [],
|
||
size: 'lg',
|
||
body: {
|
||
type: 'crud',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/read_compaction_plan`,
|
||
data: {
|
||
hdfs: '${hdfs|default:undefined}',
|
||
flink_job_id: '${flinkJobId|default:undefined}',
|
||
alias: '${tableMeta.alias|default:undefined}',
|
||
instant: '${timestamp|default:undefined}',
|
||
},
|
||
// @ts-ignore
|
||
adaptor: (payload, response) => {
|
||
return {
|
||
items: (payload['data']['operations'] ? payload['data']['operations'] : [])
|
||
// @ts-ignore
|
||
.map(operation => {
|
||
if (operation['deltaFilePaths']) {
|
||
operation.deltaFilePaths = operation.deltaFilePaths
|
||
// @ts-ignore
|
||
.map(p => {
|
||
return {
|
||
path: p,
|
||
}
|
||
})
|
||
}
|
||
return operation
|
||
}),
|
||
}
|
||
},
|
||
},
|
||
...crudCommonOptions(),
|
||
loadDataOnce: true,
|
||
...paginationTemplate(20, 8),
|
||
columns: [
|
||
{
|
||
name: 'fileId',
|
||
label: '文件 ID',
|
||
searchable: true,
|
||
},
|
||
{
|
||
name: 'baseInstantTime',
|
||
label: '版本时间点',
|
||
width: 120,
|
||
align: 'center',
|
||
},
|
||
{
|
||
name: 'partitionPath',
|
||
label: '分区',
|
||
width: 50,
|
||
align: 'center',
|
||
},
|
||
{
|
||
label: '读/写(MB)/数',
|
||
type: 'tpl',
|
||
tpl: '${metrics[\'TOTAL_IO_READ_MB\']} / ${metrics[\'TOTAL_IO_WRITE_MB\']} / ${metrics[\'TOTAL_LOG_FILES\']}',
|
||
align: 'center',
|
||
width: 90,
|
||
},
|
||
{
|
||
type: 'operation',
|
||
label: '操作',
|
||
width: 150,
|
||
buttons: [
|
||
{
|
||
label: '数据文件名',
|
||
type: 'action',
|
||
level: 'link',
|
||
size: 'xs',
|
||
actionType: 'copy',
|
||
content: '${dataFilePath}',
|
||
tooltip: '复制 ${dataFilePath}',
|
||
},
|
||
{
|
||
label: '日志文件',
|
||
type: 'action',
|
||
level: 'link',
|
||
size: 'xs',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '操作日志文件列表',
|
||
size: 'md',
|
||
...readOnlyDialogOptions(),
|
||
body: {
|
||
type: 'crud',
|
||
source: '${deltaFilePaths}',
|
||
mode: 'list',
|
||
...crudCommonOptions(),
|
||
loadDataOnce: true,
|
||
title: null,
|
||
listItem: {
|
||
title: '${path}',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
visibleOn: 'action === \'rollback\'',
|
||
type: 'action',
|
||
icon: 'fa fa-eye ml-1',
|
||
level: 'link',
|
||
tooltip: '查看回滚计划',
|
||
size: 'xs',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '回滚计划详情',
|
||
actions: [],
|
||
size: 'lg',
|
||
body: {
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/read_rollback_plan`,
|
||
data: {
|
||
hdfs: '${hdfs|default:undefined}',
|
||
flink_job_id: '${flinkJobId|default:undefined}',
|
||
alias: '${tableMeta.alias|default:undefined}',
|
||
instant: '${timestamp|default:undefined}',
|
||
},
|
||
},
|
||
body: [
|
||
{
|
||
type: 'property',
|
||
title: '回滚目标',
|
||
items: [
|
||
{
|
||
label: 'Action',
|
||
content: {
|
||
...mappingField('instantToRollback.action', hudiTimelineActionMapping),
|
||
},
|
||
},
|
||
{label: '时间点', content: '${instantToRollback.commitTime}', span: 2},
|
||
],
|
||
},
|
||
{
|
||
type: 'crud',
|
||
source: '${rollbackRequests}',
|
||
...crudCommonOptions(),
|
||
columns: [
|
||
{
|
||
name: 'fileId',
|
||
label: '文件 ID',
|
||
searchable: true,
|
||
},
|
||
{
|
||
name: 'partitionPath',
|
||
label: '分区',
|
||
width: 50,
|
||
},
|
||
{
|
||
name: 'latestBaseInstant',
|
||
label: '数据文件版本',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
visibleOn: 'action === \'clean\'',
|
||
type: 'action',
|
||
icon: 'fa fa-eye ml-1',
|
||
level: 'link',
|
||
tooltip: '查看清理计划',
|
||
size: 'xs',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '清理计划详情',
|
||
actions: [],
|
||
size: 'xl',
|
||
body: {
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/read_cleaner_plan`,
|
||
data: {
|
||
hdfs: '${hdfs|default:undefined}',
|
||
flink_job_id: '${flinkJobId|default:undefined}',
|
||
alias: '${tableMeta.alias|default:undefined}',
|
||
instant: '${timestamp|default:undefined}',
|
||
},
|
||
// @ts-ignore
|
||
adaptor: (payload, response) => {
|
||
if (payload.data['filePathsToBeDeletedPerPartition']) {
|
||
let map = payload.data['filePathsToBeDeletedPerPartition']
|
||
let list: Array<Record<string, any>> = []
|
||
Object.keys(map)
|
||
.forEach(key => {
|
||
// @ts-ignore
|
||
map[key].forEach(file => list.push({
|
||
partitionPath: key,
|
||
file: file['filePath'],
|
||
}))
|
||
})
|
||
payload.data['filePathsToBeDeletedPerPartition'] = list
|
||
}
|
||
return payload
|
||
},
|
||
},
|
||
body: [
|
||
{
|
||
type: 'property',
|
||
title: '最早回滚时间点',
|
||
items: [
|
||
{label: '策略', content: '${policy}', span: 3},
|
||
{
|
||
label: '操作',
|
||
content: {
|
||
...mappingField('earliestInstantToRetain.action', hudiTimelineActionMapping),
|
||
},
|
||
},
|
||
{
|
||
label: '状态',
|
||
content: {
|
||
...mappingField('earliestInstantToRetain.state', hudiTimelineStateMapping),
|
||
},
|
||
},
|
||
{label: '时间点', content: '${earliestInstantToRetain.timestamp}'},
|
||
],
|
||
},
|
||
{
|
||
type: 'crud',
|
||
source: '${filePathsToBeDeletedPerPartition}',
|
||
...crudCommonOptions(),
|
||
...paginationTemplate(20, 8),
|
||
loadDataOnce: true,
|
||
title: '分区删除文件',
|
||
columns: [
|
||
{
|
||
name: 'partitionPath',
|
||
label: '分区',
|
||
width: 50,
|
||
align: 'center',
|
||
},
|
||
{
|
||
name: 'file',
|
||
label: '清理文件',
|
||
className: 'nowrap',
|
||
},
|
||
{
|
||
type: 'operation',
|
||
label: '操作',
|
||
width: 100,
|
||
buttons: [
|
||
{
|
||
label: '复制路经',
|
||
type: 'action',
|
||
level: 'link',
|
||
size: 'xs',
|
||
actionType: 'copy',
|
||
content: '${file}',
|
||
tooltip: '复制 ${file}',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: 'type',
|
||
label: '来源',
|
||
width: 60,
|
||
align: 'center',
|
||
...mappingField('type', hudiTimelineTypeMapping),
|
||
filterable: filterableField(hudiTimelineTypeMapping, true),
|
||
},
|
||
]
|
||
}
|
||
|
||
export function tableMetaDialog() {
|
||
return {
|
||
title: 'Table 详情',
|
||
actions: [],
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
showCloseButton: false,
|
||
size: 'lg',
|
||
body: [
|
||
{
|
||
type: 'wrapper',
|
||
size: 'none',
|
||
body: [
|
||
{
|
||
type: 'tpl',
|
||
className: 'block font-bold mb-2',
|
||
tpl: '常用操作',
|
||
},
|
||
{
|
||
type: 'button-group',
|
||
tiled: false,
|
||
buttons: [
|
||
{
|
||
label: '同步情况',
|
||
type: 'button',
|
||
icon: 'fa fa-arrows-rotate',
|
||
actionType: 'dialog',
|
||
dialog: simpleYarnDialog(commonInfo.clusters.sync_names(), '同步详情', 'syncJobName'),
|
||
},
|
||
{
|
||
label: '压缩情况',
|
||
type: 'action',
|
||
icon: 'fa fa-minimize',
|
||
actionType: 'dialog',
|
||
dialog: simpleYarnDialog(commonInfo.clusters.compaction_names(), '压缩详情', 'compactionJobName'),
|
||
},
|
||
{
|
||
label: '历史压缩',
|
||
type: 'action',
|
||
icon: 'fa fa-list',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: 'Hudi 表时间线',
|
||
actions: [],
|
||
size: 'lg',
|
||
body: {
|
||
type: 'crud',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/table/list_compaction_metrics`,
|
||
data: {
|
||
page: '${page|default:undefined}',
|
||
count: '${perPage|default:undefined}',
|
||
order: '${orderBy|default:update_time}',
|
||
direction: '${orderDir|default:DESC}',
|
||
search_flink_job_id: '${flinkJobId|default:undefined}',
|
||
search_alias: '${tableMeta.alias|default:undefined}',
|
||
filter_completes: '${complete|default:undefined}',
|
||
},
|
||
defaultParams: {
|
||
filter_type: 'active',
|
||
},
|
||
},
|
||
...crudCommonOptions(),
|
||
perPage: 15,
|
||
headerToolbar: [
|
||
'reload',
|
||
paginationCommonOptions(),
|
||
],
|
||
footerToolbar: [
|
||
paginationCommonOptions(),
|
||
],
|
||
columns: [
|
||
{
|
||
name: 'compactionPlanInstant',
|
||
label: '压缩时间点',
|
||
width: 180,
|
||
...copyField('compactionPlanInstant'),
|
||
},
|
||
{
|
||
name: 'cluster',
|
||
label: '集群',
|
||
width: 65,
|
||
align: 'center',
|
||
type: 'tpl',
|
||
tpl: '<span class="label label-info">${cluster}</span>',
|
||
},
|
||
{
|
||
name: 'applicationId',
|
||
label: '应用',
|
||
...copyField('applicationId'),
|
||
},
|
||
{
|
||
name: 'complete',
|
||
label: '状态',
|
||
...mappingField('complete', compactionMetricsStateMapping),
|
||
filterable: filterableField(compactionMetricsStateMapping, false),
|
||
},
|
||
{
|
||
name: 'startedTime',
|
||
label: '启动时间',
|
||
...timeAndFrom('startedTime', 'startedTimeFromNow'),
|
||
sortable: true,
|
||
canAccessSuperData: false,
|
||
},
|
||
{
|
||
name: 'finishedTime',
|
||
label: '停止时间',
|
||
...timeAndFrom('finishedTime', 'finishedTimeFromNow'),
|
||
sortable: true,
|
||
align: 'center',
|
||
canAccessSuperData: false,
|
||
},
|
||
{
|
||
type: 'operation',
|
||
width: 50,
|
||
label: '操作',
|
||
fixed: 'right',
|
||
className: 'nowrap',
|
||
buttons: [
|
||
{
|
||
label: '详情',
|
||
type: 'action',
|
||
level: 'link',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '压缩详情',
|
||
size: 'xl',
|
||
actions: [],
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
showCloseButton: false,
|
||
body: [
|
||
statisticsProperty('压缩预扫描', 'before'),
|
||
{type: 'divider'},
|
||
statisticsProperty('压缩成果', 'after'),
|
||
],
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
type: 'button',
|
||
label: '时间线',
|
||
icon: 'fa fa-timeline',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: 'Hudi 表时间线',
|
||
actions: [],
|
||
size: 'lg',
|
||
body: {
|
||
type: 'crud',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/timeline/list`,
|
||
data: {
|
||
page: '${page|default:undefined}',
|
||
count: '${perPage|default:undefined}',
|
||
order: '${orderBy|default:undefined}',
|
||
direction: '${orderDir|default:undefined}',
|
||
flink_job_id: '${flinkJobId|default:undefined}',
|
||
alias: '${tableMeta.alias|default:undefined}',
|
||
filter_type: '${type|default:active}',
|
||
filter_action: '${action|default:undefined}',
|
||
filter_state: '${state|default:undefined}',
|
||
},
|
||
},
|
||
...crudCommonOptions(),
|
||
perPage: 15,
|
||
headerToolbar: [
|
||
'reload',
|
||
paginationCommonOptions(),
|
||
],
|
||
footerToolbar: [
|
||
paginationCommonOptions(),
|
||
],
|
||
columns: timelineColumns(),
|
||
},
|
||
},
|
||
},
|
||
{
|
||
type: 'button',
|
||
label: '数据目录',
|
||
icon: 'fa fa-folder',
|
||
actionType: 'dialog',
|
||
dialog: hdfsDialog('tableMeta.hudi.targetHdfsPath'),
|
||
},
|
||
{
|
||
type: 'button',
|
||
label: 'Pulsar 队列',
|
||
icon: 'fa fa-message',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '队列详情',
|
||
actions: [],
|
||
size: 'xl',
|
||
body: {
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/pulsar/topic`,
|
||
data: {
|
||
pulsar_url: '${tableMeta.pulsarAddress|default:undefined}',
|
||
topic: '${tableMeta.topic|default:undefined}',
|
||
},
|
||
},
|
||
body: [
|
||
{
|
||
type: 'property',
|
||
title: '基本信息',
|
||
items: [
|
||
{label: 'Topic', content: copyField('name'), span: 2},
|
||
{label: '最末位移', content: copyField('lastMessageId')},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'property',
|
||
title: '指标信息',
|
||
column: 4,
|
||
items: [
|
||
{label: '入队列消息速率', content: '${state.messageRateIn}'},
|
||
{
|
||
label: '出队列消息速率',
|
||
content: '${state.messageRateOut}',
|
||
},
|
||
{
|
||
label: '入队列消息吞吐量',
|
||
content: '${state.messageThroughputIn}',
|
||
},
|
||
{
|
||
label: '出队列消息吞吐量',
|
||
content: '${state.messageThroughputOut}',
|
||
},
|
||
{
|
||
label: '入队列消息数量',
|
||
content: '${state.messageInCounter}',
|
||
},
|
||
{
|
||
label: '出队列消息数量',
|
||
content: '${state.messageOutCounter}',
|
||
},
|
||
{
|
||
label: '入队列消息字节数',
|
||
content: '${state.byteInCounter}',
|
||
},
|
||
{
|
||
label: '出队列消息字节数',
|
||
content: '${state.byteOutCounter}',
|
||
},
|
||
{label: '存储消息大小', content: '${state.storageSize}'},
|
||
{label: '积压消息大小', content: '${state.backlogSize}'},
|
||
{
|
||
label: '平均消息大小',
|
||
content: '${state.averageMessageSize}',
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'table',
|
||
title: '消费者们',
|
||
source: '${subscriptionStateVOS}',
|
||
itemAction: {
|
||
type: 'action',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '详情',
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
showCloseButton: false,
|
||
actions: [],
|
||
size: 'md',
|
||
body: [
|
||
{
|
||
type: 'property',
|
||
column: 1,
|
||
items: [
|
||
{
|
||
label: '在此订阅上传递的消息的总速率(msg/s)',
|
||
content: '${messageRateOut}',
|
||
},
|
||
{
|
||
label: '此订阅提供的总吞吐量(字节/秒)',
|
||
content: '${messageThroughputOut}',
|
||
},
|
||
{
|
||
label: '传送给消费者的总字节数(字节)',
|
||
content: '${bytesOutCounter}',
|
||
},
|
||
{
|
||
label: '传递给消费者的消息总数(msg)',
|
||
content: '${messageOutCounter}',
|
||
},
|
||
{
|
||
label: '此订阅上重新传递的消息的总速率(msg/s)',
|
||
content: '${messageRateRedeliver}',
|
||
},
|
||
{
|
||
label: '分块消息调度速率',
|
||
content: '${chunkedMessageRate}',
|
||
},
|
||
{
|
||
label: '积压的大小(字节)',
|
||
content: '${backlogSize}',
|
||
},
|
||
{
|
||
label: '积压中不包含延迟消息的消息数',
|
||
content: '${messageBacklogNoDelayed}',
|
||
},
|
||
{
|
||
label: '验证订阅是否由于达到未确认消息的阈值而被阻止',
|
||
content: '${blockedSubscriptionOnUnackedMessages}',
|
||
},
|
||
{
|
||
label: '当前正在跟踪的延迟消息数',
|
||
content: '${messageDelayed}',
|
||
},
|
||
{
|
||
label: '订阅的未确认消息数',
|
||
content: '${unackedMessages}',
|
||
},
|
||
{
|
||
label: '单个活跃消费者订阅处于活跃状态的消费者名称(故障转移、独占)',
|
||
content: '${activeConsumerName}',
|
||
},
|
||
{
|
||
label: '此订阅上过期的消息总速率(msg/s)',
|
||
content: '${messageRateExpired}',
|
||
},
|
||
{
|
||
label: '此订阅上过期的消息总数',
|
||
content: '${totalMessageExpired}',
|
||
},
|
||
{
|
||
label: '最后一条消息过期时间',
|
||
content: {
|
||
type: 'tpl',
|
||
tpl: '${lastExpireTimestamp|date:YYYY-MM-DD HH\\:mm\\:ss:x}',
|
||
},
|
||
},
|
||
{
|
||
label: '上次接收的消费流命令时间',
|
||
content: {
|
||
type: 'tpl',
|
||
tpl: '${lastConsumedFlowTimestamp|date:YYYY-MM-DD HH\\:mm\\:ss:x}',
|
||
},
|
||
},
|
||
{
|
||
label: '上次消费消息时间',
|
||
content: {
|
||
type: 'tpl',
|
||
tpl: '${lastConsumedTimestamp|date:YYYY-MM-DD HH\\:mm\\:ss:x}',
|
||
},
|
||
},
|
||
{
|
||
label: '上次确认消息时间',
|
||
content: {
|
||
type: 'tpl',
|
||
tpl: '${lastAckedTimestamp|date:YYYY-MM-DD HH\\:mm\\:ss:x}',
|
||
},
|
||
},
|
||
{
|
||
label: '此订阅是持久订阅还是临时订阅',
|
||
content: '${durable}',
|
||
},
|
||
{
|
||
label: '标记订阅状态在不同区域之间保持同步',
|
||
content: '${replicated}',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
columns: [
|
||
{
|
||
name: 'name',
|
||
label: '订阅名称',
|
||
...copyField('name'),
|
||
},
|
||
{
|
||
name: 'type',
|
||
label: '订阅类型',
|
||
fixed: 'right',
|
||
...mappingField('type', subscriptionTypeMapping),
|
||
},
|
||
{name: 'messageBacklog', label: '积压', fixed: 'right'},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'table',
|
||
title: '生产者们',
|
||
source: '${state.publishers}',
|
||
itemAction: {
|
||
type: 'action',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: '详情',
|
||
closeOnEsc: true,
|
||
closeOnOutside: true,
|
||
showCloseButton: false,
|
||
actions: [],
|
||
size: 'md',
|
||
body: [
|
||
{
|
||
type: 'property',
|
||
column: 1,
|
||
items: [
|
||
{
|
||
label: '发布消息速率(msg/s)',
|
||
content: '${messageRateIn}',
|
||
},
|
||
{
|
||
label: '发布消息吞吐量(字节/秒)',
|
||
content: '${messageThroughputIn}',
|
||
},
|
||
{
|
||
label: '消息平均大小(字节)',
|
||
content: '${averageMessageSize}',
|
||
},
|
||
{
|
||
label: '接收到的分块消息总数(msg)',
|
||
content: '${chunkedMessageRate}',
|
||
},
|
||
{label: '生产者地址', content: '${address}'},
|
||
{label: '客户端版本', content: '${clientVersion}'},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
columns: [
|
||
{
|
||
name: 'producerId',
|
||
label: 'ID',
|
||
width: 50,
|
||
},
|
||
{
|
||
name: 'producerName',
|
||
label: '名称',
|
||
...copyField('producerName'),
|
||
},
|
||
{
|
||
name: 'connectedSince',
|
||
label: '连接时间',
|
||
type: 'tpl',
|
||
tpl: '${connectedSince}',
|
||
},
|
||
{
|
||
name: 'accessMode',
|
||
label: '发布类型',
|
||
fixed: 'right',
|
||
...mappingField('accessMode', publishTypeMapping),
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
type: 'button',
|
||
label: 'Hudi 表结构',
|
||
icon: 'fa fa-table',
|
||
actionType: 'dialog',
|
||
dialog: {
|
||
title: 'Hudi 表结构',
|
||
actions: [],
|
||
body: {
|
||
type: 'service',
|
||
api: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/hudi/schema`,
|
||
data: {
|
||
flink_job_id: '${flinkJobId|default:undefined}',
|
||
alias: '${tableMeta.alias|default:undefined}',
|
||
},
|
||
},
|
||
body: {
|
||
type: 'page',
|
||
body: {
|
||
type: 'json',
|
||
source: '${detail}',
|
||
levelExpand: 3,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider', visibleOn: '${syncRuntime}'},
|
||
{
|
||
visibleOn: '${syncRuntime}',
|
||
...runMetaProperty('sync'),
|
||
},
|
||
{type: 'divider', visibleOn: '${compactionRuntime}'},
|
||
{
|
||
visibleOn: '${compactionRuntime}',
|
||
...runMetaProperty('compaction'),
|
||
},
|
||
{type: 'divider'},
|
||
flinkJobProperty('flinkJobId', 'flinkJob.name', 'flinkJob.runMode', 'flinkJob.tags'),
|
||
{type: 'divider'},
|
||
{
|
||
type: 'property',
|
||
title: 'Table 配置',
|
||
items: [
|
||
{label: '别名', content: copyField('tableMeta.alias', '复制别名')},
|
||
{
|
||
label: '分片表',
|
||
content: '${IF(tableMeta.type === \'sharding\', \'是\', \'否\')}',
|
||
},
|
||
{label: '分区字段', content: copyField('tableMeta.partitionField', '复制分区字段')},
|
||
{label: '源端', content: copyField('tableMeta.source', '复制源端')},
|
||
{label: '源库名', content: copyField('tableMeta.schema', '复制库名')},
|
||
{label: '源表名', content: copyField('tableMeta.table', '复制表名')},
|
||
{label: '源表类型', content: '${tableMeta.sourceType}'},
|
||
{label: '优先级', content: '${tableMeta.priority}', span: 2},
|
||
{
|
||
label: '标签',
|
||
content: {
|
||
type: 'each',
|
||
source: '${SPLIT(tableMeta.tags, ",")}',
|
||
items: mappingField('item', tagsMapping),
|
||
},
|
||
span: 3,
|
||
},
|
||
{label: '订阅 Topic', content: copyField('tableMeta.topic', '复制 Topic'), span: 3},
|
||
{
|
||
label: 'Pulsar 地址',
|
||
content: copyField('tableMeta.pulsarAddress', '复制地址', 130),
|
||
span: 3,
|
||
},
|
||
{label: '过滤模式', content: mappingField('tableMeta.filterType', filterModeMapping)},
|
||
{label: '过滤字段', content: '${tableMeta.filterField}', span: 2},
|
||
{
|
||
label: '过滤内容',
|
||
content: {
|
||
type: 'each',
|
||
source: '${SPLIT(tableMeta.filterValues, ",")}',
|
||
items: {
|
||
type: 'tpl',
|
||
tpl: '<span class=\'label bg-info mr-1\'>${item}</span>',
|
||
},
|
||
},
|
||
span: 3,
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'property',
|
||
title: 'Hudi 配置',
|
||
items: [
|
||
{label: '表类型', content: mappingField('tableMeta.hudi.targetTableType', hudiTableTypeMapping)},
|
||
{label: '库名', content: copyField('tableMeta.hudi.targetDataSource', '复制库名')},
|
||
{label: '表名', content: copyField('tableMeta.hudi.targetTable', '复制表名')},
|
||
{
|
||
label: 'HDFS',
|
||
content: copyField('tableMeta.hudi.targetHdfsPath', '复制路径'),
|
||
span: 3,
|
||
},
|
||
{
|
||
label: 'Bucket 数量',
|
||
content: '${tableMeta.hudi.bucketIndexNumber}',
|
||
},
|
||
{
|
||
label: '保留文件版本',
|
||
content: '${tableMeta.hudi.keepFileVersion}',
|
||
},
|
||
{
|
||
label: '保留提交个数',
|
||
content: '${tableMeta.hudi.keepCommitVersion}',
|
||
},
|
||
{label: '写并行度', content: '${tableMeta.hudi.writeTasks}'},
|
||
{label: '读并行度', content: '${tableMeta.hudi.sourceTasks}'},
|
||
{
|
||
label: '压缩并行度',
|
||
content: '${tableMeta.hudi.compactionTasks}',
|
||
},
|
||
{
|
||
label: '写任务最大内存',
|
||
content: '${tableMeta.hudi. writeTaskMaxMemory}M',
|
||
},
|
||
{
|
||
label: '写批次大小',
|
||
content: '${tableMeta.hudi.writeBatchSize}M',
|
||
},
|
||
{
|
||
label: '写限速',
|
||
content: '${tableMeta.hudi.writeRateLimit}条/秒',
|
||
},
|
||
{
|
||
label: '压缩策略',
|
||
content: '${tableMeta.hudi. compactionStrategy}',
|
||
},
|
||
{
|
||
label: '压缩增量个数',
|
||
content: '${tableMeta.hudi.compactionDeltaCommits}',
|
||
},
|
||
{
|
||
label: '压缩增量时间',
|
||
content: '${tableMeta.hudi.compactionDeltaSeconds}秒',
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'property',
|
||
title: 'Yarn 配置',
|
||
column: 2,
|
||
items: [
|
||
{
|
||
label: '同步 JM 内存',
|
||
content: '${tableMeta.syncYarn.jobManagerMemory}M',
|
||
},
|
||
{
|
||
label: '同步 TM 内存',
|
||
content: '${tableMeta.syncYarn.taskManagerMemory}M',
|
||
},
|
||
{
|
||
label: '压缩 JM 内存',
|
||
content: '${tableMeta.compactionYarn.jobManagerMemory}M',
|
||
},
|
||
{
|
||
label: '压缩 TM 内存',
|
||
content: '${tableMeta.compactionYarn.taskManagerMemory}M',
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'property',
|
||
title: '其他配置',
|
||
column: 4,
|
||
items: [
|
||
{
|
||
label: '指标发布地址',
|
||
content: copyField('tableMeta.config.metricPublishUrl'),
|
||
span: 4,
|
||
},
|
||
{
|
||
label: '指标打点延迟',
|
||
content: '${tableMeta.config.metricPublishDelay}',
|
||
},
|
||
{
|
||
label: '指标打点间隔',
|
||
content: '${tableMeta.config.metricPublishPeriod}',
|
||
},
|
||
{
|
||
label: '指标发布超时',
|
||
content: '${tableMeta.config.metricPublishTimeout}',
|
||
},
|
||
{
|
||
label: '指标批次数量',
|
||
content: '${tableMeta.config.metricPublishBatch}',
|
||
},
|
||
{
|
||
label: 'Prometheus 地址',
|
||
content: copyField('tableMeta.config.metricPrometheusUrl'),
|
||
span: 4,
|
||
},
|
||
{
|
||
label: '事件提交服务地址',
|
||
content: copyField('tableMeta.config.metricApiUrl', '复制 URL', 130),
|
||
span: 4,
|
||
},
|
||
{
|
||
label: 'Checkpoint 存储',
|
||
content: copyField('tableMeta.config.checkpointRootPath'),
|
||
span: 4,
|
||
},
|
||
{
|
||
label: 'Zookeeper 地址',
|
||
content: copyField('tableMeta.config.zookeeperUrl'),
|
||
span: 4,
|
||
},
|
||
],
|
||
},
|
||
{type: 'divider'},
|
||
{
|
||
type: 'table',
|
||
title: '表字段详情',
|
||
source: '${tableMeta.fields}',
|
||
resizable: false,
|
||
columns: [
|
||
{name: 'sequence', label: '排序', width: 50, align: 'center'},
|
||
{name: 'name', label: '字段名'},
|
||
{name: 'type', label: '字段类型', width: 100},
|
||
{name: 'length', label: '字段长度', width: 60, align: 'center'},
|
||
{name: 'scala', label: '字段精度', width: 60, align: 'center'},
|
||
{
|
||
label: '是否主键',
|
||
align: 'center',
|
||
width: 50,
|
||
type: 'tpl',
|
||
tpl: '${primaryKey|isTrue:\'<i class="fa fa-check-circle text-success text-md p-0"></i>\':\'<i></i>\'|raw}',
|
||
},
|
||
{
|
||
label: '是否分片键',
|
||
align: 'center',
|
||
width: 70,
|
||
type: 'tpl',
|
||
tpl: '${partitionKey|isTrue:\'<i class="fa fa-check-circle text-success text-md p-0"></i>\':\'<i></i>\'|raw}',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
export function mappingItem(label: string, value: string, color = 'bg-info') {
|
||
return {
|
||
label: label,
|
||
value: value,
|
||
color: color,
|
||
}
|
||
}
|
||
|
||
export const runModeMapping = [
|
||
mappingItem('1对1', 'ONE_IN_ONE', 'bg-pink-300'),
|
||
mappingItem('1对多', 'ALL_IN_ONE', 'bg-purple-300'),
|
||
mappingItem('按表1对多', 'ALL_IN_ONE_BY_TABLE', 'bg-cyan-300'),
|
||
mappingItem('按库1对多', 'ALL_IN_ONE_BY_SCHEMA', 'bg-indigo-300'),
|
||
]
|
||
|
||
export const compactionStatusMapping = [
|
||
mappingItem('调度', 'SCHEDULE'),
|
||
mappingItem('开始', 'START', 'bg-primary'),
|
||
mappingItem('完成', 'FINISH', 'bg-success'),
|
||
mappingItem('失败', 'FAIL', 'bg-danger'),
|
||
]
|
||
|
||
export const tagsMapping = [
|
||
mappingItem('不压缩', 'NO_COMPACT'),
|
||
mappingItem('不调度压缩', 'NO_SCHEDULE_COMPACT'),
|
||
mappingItem('备份Pulsar消息', 'PULSAR_BACKUP'),
|
||
mappingItem('无预合并', 'NO_PRE_COMBINE'),
|
||
mappingItem('不忽略写日志错误', 'NO_IGNORE_FAILED'),
|
||
mappingItem('取消算子合并', 'DISABLE_CHAINING'),
|
||
mappingItem('跟踪压缩op_ts', 'TRACE_LATEST_OP_TS'),
|
||
mappingItem('不使用HSync', 'DISABLE_HSYNC'),
|
||
mappingItem('测试包', 'USE_TEST_JAR'),
|
||
]
|
||
|
||
export const hudiTableTypeMapping = [
|
||
mappingItem('MOR', 'MERGE_ON_READ'),
|
||
mappingItem('COW', 'COPY_ON_WRITE'),
|
||
]
|
||
|
||
export const filterModeMapping = [
|
||
mappingItem('无', 'NONE', 'bg-pink-500'),
|
||
mappingItem('包含模式', 'INCLUDE', 'bg-purple-500'),
|
||
mappingItem('排除模式', 'EXCLUDE', 'bg-cyan-500'),
|
||
]
|
||
|
||
export const subscriptionTypeMapping = [
|
||
mappingItem('独占', 'Exclusive', 'bg-pink-500'),
|
||
mappingItem('共享', 'Shared', 'bg-purple-500'),
|
||
mappingItem('灾备', 'Failover', 'bg-cyan-500'),
|
||
mappingItem('Key', 'Key_Shared', 'bg-green-500'),
|
||
]
|
||
|
||
export const publishTypeMapping = [
|
||
mappingItem('共享', 'Shared', 'bg-pink-500'),
|
||
mappingItem('独占', 'Exclusive', 'bg-purple-500'),
|
||
mappingItem('等待独占', 'WaiteForExclusive', 'bg-cyan-500'),
|
||
]
|
||
|
||
export const hudiTimelineActionMapping = [
|
||
mappingItem('Commit', 'commit'),
|
||
mappingItem('Delta Commit', 'deltacommit'),
|
||
mappingItem('Clean', 'clean', 'bg-cyan-500'),
|
||
mappingItem('Rollback', 'rollback', 'label-danger'),
|
||
mappingItem('Savepoint', 'savepoint'),
|
||
mappingItem('Replace Commit', 'replacecommit', 'label-warning'),
|
||
mappingItem('Compaction', 'compaction', 'bg-purple-500'),
|
||
mappingItem('Restore', 'restore', 'label-warning'),
|
||
mappingItem('Indexing', 'indexing'),
|
||
mappingItem('Schema Commit', 'schemacommit', 'label-warning'),
|
||
]
|
||
|
||
export const hudiTimelineStateMapping = [
|
||
mappingItem('已提交', 'REQUESTED'),
|
||
mappingItem('操作中', 'INFLIGHT', 'label-warning'),
|
||
mappingItem('已完成', 'COMPLETED', 'label-success'),
|
||
mappingItem('错误', 'INVALID', 'label-danger'),
|
||
]
|
||
|
||
export const hudiTimelineTypeMapping = [
|
||
mappingItem('活跃', 'active'),
|
||
mappingItem('归档', 'archive', 'bg-gray-300'),
|
||
]
|
||
|
||
export const tableRunningStateMapping = [
|
||
mappingItem('运行中', 'true', 'label-success'),
|
||
mappingItem('未运行', 'false'),
|
||
]
|
||
|
||
export const versionUpdateStateMapping = [
|
||
mappingItem('已跨天', 'true', 'label-success'),
|
||
mappingItem('未跨天', 'false', 'label-danger'),
|
||
]
|
||
|
||
export const compactionMetricsStateMapping = [
|
||
mappingItem('成功', 'true', 'label-success'),
|
||
mappingItem('失败', 'false', 'label-danger'),
|
||
]
|
||
|
||
export function mappingField(field: string, mapping: Array<Record<string, string>>) {
|
||
let mapData: Record<string, string> = {
|
||
'*': `<span class='label bg-gray-300'>\${${field}}</span>`,
|
||
}
|
||
mapping.forEach(item => {
|
||
mapData[item['value']] = `<span class='label ${item['color']}'>${item['label']}</span>`
|
||
})
|
||
return {
|
||
type: 'mapping',
|
||
value: `\${${field}}`,
|
||
map: mapData,
|
||
}
|
||
}
|
||
|
||
export function filterableField(mapping: Array<Record<string, any>>, multiple = false) {
|
||
return {
|
||
multiple: multiple,
|
||
options: [
|
||
...mapping,
|
||
],
|
||
}
|
||
}
|
||
|
||
export function formReloadFlinkJobIdTextInputAndAliasTextInput(id: string) {
|
||
return {
|
||
onEvent: {
|
||
change: {
|
||
actions: [
|
||
{
|
||
actionType: 'reload',
|
||
componentId: `flink-job-id-input-select-${id}`,
|
||
},
|
||
{
|
||
actionType: 'reload',
|
||
componentId: `alias-input-select-${id}`,
|
||
},
|
||
],
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
export function flinkJobIdTextInput(id: string, require = false) {
|
||
return {
|
||
id: `flink-job-id-input-select-${id}`,
|
||
type: 'select',
|
||
name: 'flinkJobId',
|
||
label: 'Flink job id',
|
||
placeholder: '通过 ID 搜索',
|
||
clearable: true,
|
||
required: require,
|
||
searchable: true,
|
||
source: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/table/all_flink_job_id`,
|
||
data: {
|
||
alias: '${alias|default:undefined}',
|
||
},
|
||
},
|
||
/*onEvent: {
|
||
change: {
|
||
actions: [
|
||
{
|
||
actionType: 'reload',
|
||
componentId: `alias-input-select-${id}`,
|
||
},
|
||
]
|
||
}
|
||
}*/
|
||
}
|
||
}
|
||
|
||
export function aliasTextInput(id: string, require = false) {
|
||
return {
|
||
id: `alias-input-select-${id}`,
|
||
type: 'select',
|
||
name: 'alias',
|
||
label: '别名',
|
||
placeholder: '通过别名搜索',
|
||
clearable: true,
|
||
required: require,
|
||
searchable: true,
|
||
source: {
|
||
method: 'get',
|
||
url: `${commonInfo.baseUrl}/table/all_alias`,
|
||
data: {
|
||
flink_job_id: '${flinkJobId|default:undefined}',
|
||
},
|
||
},
|
||
/*onEvent: {
|
||
change: {
|
||
actions: [
|
||
{
|
||
actionType: 'reload',
|
||
componentId: `flink-job-id-input-select-${id}`
|
||
},
|
||
]
|
||
}
|
||
}*/
|
||
}
|
||
}
|
||
|
||
export function time(field: string) {
|
||
return {
|
||
type: 'tpl',
|
||
tpl: `\${IF(${field}, DATETOSTR(${field}, 'YYYY-MM-DD HH:mm:ss'), undefined)}`,
|
||
}
|
||
}
|
||
|
||
export function pictureFromIds(field: string) {
|
||
return `\${ARRAYMAP(${field},id => '${commonInfo.baseAiUrl}/upload/download/' + id)}`
|
||
}
|
||
|
||
const formInputFileStaticColumns = [
|
||
{
|
||
name: 'filename',
|
||
label: '文件名',
|
||
},
|
||
{
|
||
type: 'operation',
|
||
label: '操作',
|
||
width: 140,
|
||
buttons: [
|
||
{
|
||
type: 'action',
|
||
label: '预览',
|
||
level: 'link',
|
||
icon: 'fas fa-eye'
|
||
},
|
||
{
|
||
type: 'action',
|
||
label: '下载',
|
||
level: 'link',
|
||
icon: 'fa fa-download',
|
||
actionType: 'ajax',
|
||
// api: {
|
||
// ...apiGet('${base}/upload/download/${id}'),
|
||
// responseType: 'blob',
|
||
// }
|
||
}
|
||
]
|
||
}
|
||
]
|
||
|
||
export function formInputSingleFileStatic(field: string, label: string) {
|
||
return {
|
||
visibleOn: '${static}',
|
||
type: 'control',
|
||
label: label,
|
||
required: true,
|
||
body: {
|
||
type: 'table',
|
||
source: `\${${field}|asArray}`,
|
||
columns: formInputFileStaticColumns,
|
||
},
|
||
}
|
||
}
|
||
|
||
export function formInputMultiFileStatic(field: string, label: string) {
|
||
return {
|
||
visibleOn: '${static}',
|
||
type: 'input-table',
|
||
label: label,
|
||
name: field,
|
||
required: true,
|
||
resizable: false,
|
||
columns: formInputFileStaticColumns,
|
||
}
|
||
} |