This is an automated email from the ASF dual-hosted git repository.
dockerzhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git
The following commit(s) were added to refs/heads/master by this push:
new 18329baa8d [INLONG-9439][Dashboard] Support module audit function
(#9446)
18329baa8d is described below
commit 18329baa8dd0f0756e75a792a8585775d9508156
Author: Lizhen <[email protected]>
AuthorDate: Fri Dec 8 16:37:51 2023 +0800
[INLONG-9439][Dashboard] Support module audit function (#9446)
---
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 20ef99b2da..b08d29c217 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -835,5 +835,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 478231fbfb..94fd002ff9 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -835,5 +835,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;