This is an automated email from the ASF dual-hosted git repository. vernedeng pushed a commit to branch branch-1.10 in repository https://gitbox.apache.org/repos/asf/inlong.git
commit ef16f73ca19de48c51dd3f6f92b0208a9caa8327 Author: Lizhen <88174078+bluew...@users.noreply.github.com> AuthorDate: Fri Dec 8 16:37:51 2023 +0800 [INLONG-9439][Dashboard] Support module audit function (#9446) (cherry picked from commit 18329baa8dd0f0756e75a792a8585775d9508156) --- inlong-dashboard/src/configs/menus/conf.tsx | 12 ++ inlong-dashboard/src/configs/routes/conf.ts | 4 + inlong-dashboard/src/i18n.ts | 4 + inlong-dashboard/src/ui/locales/cn.json | 5 +- inlong-dashboard/src/ui/locales/en.json | 5 +- .../src/ui/pages/ModuleAuditDashboard/config.tsx | 179 +++++++++++++++++++++ .../src/ui/pages/ModuleAuditDashboard/index.tsx | 118 ++++++++++++++ 7 files changed, 325 insertions(+), 2 deletions(-) diff --git a/inlong-dashboard/src/configs/menus/conf.tsx b/inlong-dashboard/src/configs/menus/conf.tsx index 00b1bb441f..29cde12f70 100644 --- a/inlong-dashboard/src/configs/menus/conf.tsx +++ b/inlong-dashboard/src/configs/menus/conf.tsx @@ -27,6 +27,7 @@ import { SafetyOutlined, ShopOutlined, InteractionOutlined, + ProfileOutlined, } from '@ant-design/icons'; import type { MenuItemType } from '.'; @@ -91,6 +92,17 @@ const conf: MenuItemType[] = [ }, ], }, + { + name: i18n.t('configs.menus.SystemOperation'), + icon: <ProfileOutlined />, + isAdmin: true, + children: [ + { + path: '/system', + name: i18n.t('configs.menus.ModuleAudit'), + }, + ], + }, ]; export default conf; diff --git a/inlong-dashboard/src/configs/routes/conf.ts b/inlong-dashboard/src/configs/routes/conf.ts index 667f0c3f91..cc73f36988 100644 --- a/inlong-dashboard/src/configs/routes/conf.ts +++ b/inlong-dashboard/src/configs/routes/conf.ts @@ -115,6 +115,10 @@ const conf: RouteProps[] = [ path: '/tenant', component: () => import('@/ui/pages/TenantManagement'), }, + { + path: '/system', + component: () => import('@/ui/pages/ModuleAuditDashboard'), + }, { component: () => import('@/ui/pages/Error/404'), }, diff --git a/inlong-dashboard/src/i18n.ts b/inlong-dashboard/src/i18n.ts index 36457c022d..6a0d7f6de4 100644 --- a/inlong-dashboard/src/i18n.ts +++ b/inlong-dashboard/src/i18n.ts @@ -36,6 +36,8 @@ const resources = { 'configs.menus.Nodes': 'DataNodes', 'configs.menus.DataSynchronize': 'Synchronization', 'configs.menus.TenantManagement': 'Tenant Management', + 'configs.menus.SystemOperation': 'Operation', + 'configs.menus.ModuleAudit': 'Module audit', }, }, cn: { @@ -51,6 +53,8 @@ const resources = { 'configs.menus.Nodes': '数据节点', 'configs.menus.DataSynchronize': '数据同步', 'configs.menus.TenantManagement': '租户管理', + 'configs.menus.SystemOperation': '系统运维', + 'configs.menus.ModuleAudit': '模块审计', }, }, }; diff --git a/inlong-dashboard/src/ui/locales/cn.json b/inlong-dashboard/src/ui/locales/cn.json index 540c485122..340f1f2c43 100644 --- a/inlong-dashboard/src/ui/locales/cn.json +++ b/inlong-dashboard/src/ui/locales/cn.json @@ -830,5 +830,8 @@ "pages.Tenant.config.Admin": "租户管理员", "pages.Tenant.config.GeneralUser": "普通用户", "pages.Tenant.config.Creator": "创建人", - "pages.Tenant.config.CreateTime": "创建时间" + "pages.Tenant.config.CreateTime": "创建时间", + "pages.ModuleAuditDashboard.config.Ip": "机器 IP", + "pages.ModuleAuditDashboard.config.BenchmarkIndicator": "基准指标", + "pages.ModuleAuditDashboard.config.ComparativeIndicators": "对比指标" } diff --git a/inlong-dashboard/src/ui/locales/en.json b/inlong-dashboard/src/ui/locales/en.json index 4203413bc5..28d20ba465 100644 --- a/inlong-dashboard/src/ui/locales/en.json +++ b/inlong-dashboard/src/ui/locales/en.json @@ -829,5 +829,8 @@ "pages.Tenant.config.Admin": "Tenant admin", "pages.Tenant.config.GeneralUser": "General user", "pages.Tenant.config.Creator": "Creator", - "pages.Tenant.config.CreateTime": "Create time" + "pages.Tenant.config.CreateTime": "Create time", + "pages.ModuleAuditDashboard.config.Ip": "Machine ip", + "pages.ModuleAuditDashboard.config.BenchmarkIndicator": "Benchmark indicator", + "pages.ModuleAuditDashboard.config.ComparativeIndicators": "Comparative indicator" } diff --git a/inlong-dashboard/src/ui/pages/ModuleAuditDashboard/config.tsx b/inlong-dashboard/src/ui/pages/ModuleAuditDashboard/config.tsx new file mode 100644 index 0000000000..43c119ec36 --- /dev/null +++ b/inlong-dashboard/src/ui/pages/ModuleAuditDashboard/config.tsx @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import dayjs from 'dayjs'; +import i18n from '@/i18n'; + +export const timeStaticsDimList = [ + { + label: i18n.t('pages.GroupDetail.Audit.Min'), + value: 'MINUTE', + }, + { + label: i18n.t('pages.GroupDetail.Audit.Hour'), + value: 'HOUR', + }, + { + label: i18n.t('pages.GroupDetail.Audit.Day'), + value: 'DAY', + }, +]; + +export const toChartData = (source, sourceDataMap) => { + const xAxisData = Object.keys(sourceDataMap); + return { + legend: { + data: source.map(item => item.auditName), + }, + tooltip: { + trigger: 'axis', + }, + xAxis: { + type: 'category', + data: xAxisData, + }, + yAxis: { + type: 'value', + }, + series: source.map(item => ({ + name: item.auditName, + type: 'line', + data: xAxisData.map(logTs => sourceDataMap[logTs]?.[item.auditId] || 0), + })), + }; +}; + +export const toTableData = (source, sourceDataMap) => { + return Object.keys(sourceDataMap) + .reverse() + .map(logTs => ({ + ...sourceDataMap[logTs], + logTs, + })); +}; + +export const getFormContent = (initialValues, onSearch) => [ + { + type: 'inputsearch', + label: i18n.t('pages.ModuleAuditDashboard.config.Ip'), + name: 'ip', + }, + { + type: 'datepicker', + label: i18n.t('pages.GroupDetail.Audit.StartDate'), + name: 'startDate', + initialValue: dayjs(initialValues.startDate), + props: { + allowClear: false, + format: 'YYYY-MM-DD', + }, + }, + { + type: 'datepicker', + label: i18n.t('pages.GroupDetail.Audit.EndDate'), + name: 'endDate', + initialValues: dayjs(initialValues.endDate), + props: { + allowClear: false, + format: 'YYYY-MM-DD', + disabledDate: current => { + const start = dayjs(initialValues.startDate); + const dim = initialValues.timeStaticsDim; + if (dim === 'HOUR' || dim === 'DAY') { + const tooLate = current && current <= start.endOf('day'); + const tooEarly = start && current > start.add(7, 'd').endOf('day'); + return tooLate || tooEarly; + } + const tooLate = current && current >= start.endOf('day'); + const tooEarly = start && current < start.add(-1, 'd').endOf('day'); + return tooLate || tooEarly; + }, + }, + }, + { + type: 'select', + label: i18n.t('pages.GroupDetail.Audit.TimeStaticsDim'), + name: 'timeStaticsDim', + initialValue: initialValues.timeStaticsDim, + props: { + dropdownMatchSelectWidth: false, + options: timeStaticsDimList, + }, + }, + { + type: 'select', + label: i18n.t('pages.ModuleAuditDashboard.config.BenchmarkIndicator'), + name: 'benchmark', + props: { + allowClear: true, + dropdownMatchSelectWidth: false, + options: { + requestAuto: true, + requestService: { + url: '/audit/getAuditBases', + method: 'GET', + }, + requestParams: { + formatResult: result => + result?.map(item => ({ + label: item.name, + value: item.auditId, + })) || [], + }, + }, + }, + }, + { + type: 'select', + label: i18n.t('pages.ModuleAuditDashboard.config.ComparativeIndicators'), + name: 'compared', + props: { + allowClear: true, + dropdownMatchSelectWidth: false, + options: { + requestAuto: true, + requestService: { + url: '/audit/getAuditBases', + method: 'GET', + }, + requestParams: { + formatResult: result => + result?.map(item => ({ + label: item.name, + value: item.auditId, + })) || [], + }, + }, + }, + }, +]; + +export const getTableColumns = source => { + const data = source.map(item => ({ + title: item.auditName, + dataIndex: item.auditId, + render: text => text || 0, + })); + return [ + { + title: i18n.t('pages.GroupDetail.Audit.Time'), + dataIndex: 'logTs', + }, + ].concat(data); +}; diff --git a/inlong-dashboard/src/ui/pages/ModuleAuditDashboard/index.tsx b/inlong-dashboard/src/ui/pages/ModuleAuditDashboard/index.tsx new file mode 100644 index 0000000000..8136960163 --- /dev/null +++ b/inlong-dashboard/src/ui/pages/ModuleAuditDashboard/index.tsx @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useMemo, useState } from 'react'; +import { useForm } from '@/ui/components/FormGenerator'; +import HighTable from '@/ui/components/HighTable'; +import { useRequest } from '@/ui/hooks'; +import { timestampFormat } from '@/core/utils'; +import { getFormContent, toTableData, getTableColumns, timeStaticsDimList } from './config'; + +const Comp: React.FC = () => { + const [form] = useForm(); + + const [query, setQuery] = useState({ + startDate: +new Date(), + endDate: +new Date(), + auditIds: ['3', '4'], + timeStaticsDim: timeStaticsDimList[0].value, + }); + + const { data: sourceData = [], run } = useRequest( + { + url: '/audit/list', + method: 'POST', + data: { + ...query, + startDate: timestampFormat(query.startDate, 'yyyy-MM-dd'), + endDate: timestampFormat(query.endDate, 'yyyy-MM-dd'), + }, + }, + { + refreshDeps: [query], + formatResult: result => result.sort((a, b) => (a.auditId - b.auditId > 0 ? 1 : -1)), + }, + ); + + const sourceDataMap = useMemo(() => { + const flatArr = sourceData + .reduce( + (acc, cur) => + acc.concat( + cur.auditSet.map(item => ({ + ...item, + auditId: cur.auditId, + })), + ), + [], + ) + .sort((a, b) => { + const aT = +new Date(query.timeStaticsDim === 'HOUR' ? `${a.logTs}:00` : a.logTs); + const bT = +new Date(query.timeStaticsDim === 'HOUR' ? `${b.logTs}:00` : b.logTs); + return aT - bT; + }); + const output = flatArr.reduce((acc, cur) => { + if (!acc[cur.logTs]) { + acc[cur.logTs] = {}; + } + acc[cur.logTs] = { + ...acc[cur.logTs], + [cur.auditId]: cur.count, + }; + return acc; + }, {}); + return output; + }, [sourceData, query.timeStaticsDim]); + + const onSearch = async () => { + await form.validateFields(); + run(); + }; + + const onFilter = keyword => { + setQuery({ + ...query, + ...keyword, + auditIds: + keyword.benchmark !== undefined && keyword.compared !== undefined + ? [keyword.benchmark, keyword.compared] + : ['3', '4'], + startDate: +keyword.startDate.$d, + endDate: +keyword.startDate.$d, + }); + }; + + return ( + <> + <HighTable + filterForm={{ + content: getFormContent(query, onSearch), + onFilter, + }} + table={{ + columns: getTableColumns(sourceData), + dataSource: toTableData(sourceData, sourceDataMap), + rowKey: 'logTs', + }} + /> + </> + ); +}; + +export default Comp;