diff --git a/web/package.json b/web/package.json
index 3f8e0ae5..29e811a5 100644
--- a/web/package.json
+++ b/web/package.json
@@ -68,6 +68,7 @@
"re-resizable": "^6.9.1",
"react": "^16.5.1",
"react-calendar-heatmap": "^1.8.1",
+ "react-color": "^2.19.3",
"react-container-query": "^0.11.0",
"react-copy-to-clipboard": "^5.0.1",
"react-dnd": "^14.0.4",
diff --git a/web/src/components/Icons/Convert.jsx b/web/src/components/Icons/Convert.jsx
new file mode 100644
index 00000000..ca821cc1
--- /dev/null
+++ b/web/src/components/Icons/Convert.jsx
@@ -0,0 +1,15 @@
+export default () => {
+ return (
+
+ );
+ };
+
\ No newline at end of file
diff --git a/web/src/components/Icons/Treemap.jsx b/web/src/components/Icons/Treemap.jsx
new file mode 100644
index 00000000..333d46f9
--- /dev/null
+++ b/web/src/components/Icons/Treemap.jsx
@@ -0,0 +1,15 @@
+export default () => {
+ return (
+
+ );
+ };
+
\ No newline at end of file
diff --git a/web/src/models/global.js b/web/src/models/global.js
index 6a20e040..1d455371 100644
--- a/web/src/models/global.js
+++ b/web/src/models/global.js
@@ -354,6 +354,9 @@ export default {
updateCluster(state, { payload }) {
let idx = state.clusterList.findIndex((item) => item.id === payload.id);
idx > -1 && (state.clusterList[idx].name = payload.name);
+ if (state.selectedCluster?.id === payload.id) {
+ state.selectedCluster.monitor_configs = payload.monitor_configs
+ }
state.clusterStatus[payload.id].config.monitored = payload.monitored;
return state;
},
diff --git a/web/src/pages/DataManagement/Insight/Visualization/Helper/ListItem.tsx b/web/src/pages/DataManagement/Insight/Visualization/Helper/ListItem.tsx
index 5020d6e2..8320dd08 100644
--- a/web/src/pages/DataManagement/Insight/Visualization/Helper/ListItem.tsx
+++ b/web/src/pages/DataManagement/Insight/Visualization/Helper/ListItem.tsx
@@ -31,7 +31,9 @@ export default (props: IMeta & {
items,
bucketSize: queries.getBucketSize(),
});
- setData(res)
+ if (res && !res.error) {
+ setData(res.data)
+ }
setLoading(false)
}
diff --git a/web/src/pages/DataManagement/Insight/Visualization/Widget/WidgetBody/Chart.tsx b/web/src/pages/DataManagement/Insight/Visualization/Widget/WidgetBody/Chart.tsx
index bd580fbe..7f474e61 100644
--- a/web/src/pages/DataManagement/Insight/Visualization/Widget/WidgetBody/Chart.tsx
+++ b/web/src/pages/DataManagement/Insight/Visualization/Widget/WidgetBody/Chart.tsx
@@ -35,7 +35,9 @@ export default (props: IProps) => {
...metric,
bucketSize: queries.getBucketSize(),
});
- setData(res)
+ if (res && !res.error) {
+ setData(res.data)
+ }
setLoading(false)
}
diff --git a/web/src/pages/DataManagement/View/Widget/WidgetBody/Chart.jsx b/web/src/pages/DataManagement/View/Widget/WidgetBody/Chart.jsx
index 8333ecfa..24168856 100644
--- a/web/src/pages/DataManagement/View/Widget/WidgetBody/Chart.jsx
+++ b/web/src/pages/DataManagement/View/Widget/WidgetBody/Chart.jsx
@@ -222,7 +222,7 @@ export default (props) => {
setLoading(false);
return;
}
- const newData = res.map((item) => Array.isArray(item) ? item : []);
+ const newData = res.map((item) => Array.isArray(item.data) ? item.data : []);
let group_mapping
if (is_layered) {
group_mapping = group_labels[layer_index]
diff --git a/web/src/pages/Platform/Overview/Cluster/Monitor/TopN.jsx b/web/src/pages/Platform/Overview/Cluster/Monitor/TopN.jsx
new file mode 100644
index 00000000..7282e11a
--- /dev/null
+++ b/web/src/pages/Platform/Overview/Cluster/Monitor/TopN.jsx
@@ -0,0 +1,67 @@
+import { Icon, Tabs } from "antd";
+import { formatMessage } from "umi/locale";
+import { useMemo, useState } from "react";
+import NodeMetric from "../../components/node_metric";
+import IndexMetric from "../../components/index_metric";
+import ClusterMetric from "../../components/cluster_metric";
+import QueueMetric from "../../components/queue_metric";
+import { ESPrefix } from "@/services/common";
+import { SearchEngines } from "@/lib/search_engines";
+import TopN from "../../components/TopN";
+
+export default (props) => {
+
+ const { isAgent } = props
+
+ const [param, setParam] = useState({
+ tab: "node",
+ });
+ return (
+ {
+ setParam({
+ tab: key,
+ });
+ }}
+ >
+
+
+
+ {
+ !isAgent && (
+
+
+
+ )
+ }
+ {
+ isAgent && (
+
+
+
+ )
+ }
+
+ );
+}
diff --git a/web/src/pages/Platform/Overview/Cluster/Monitor/index.jsx b/web/src/pages/Platform/Overview/Cluster/Monitor/index.jsx
index b141370d..1ee45594 100644
--- a/web/src/pages/Platform/Overview/Cluster/Monitor/index.jsx
+++ b/web/src/pages/Platform/Overview/Cluster/Monitor/index.jsx
@@ -8,10 +8,12 @@ import { formatMessage } from "umi/locale";
import Monitor from "@/components/Overview/Monitor";
import StatisticBar from "./statistic_bar";
import { Empty } from "antd";
+import TopN from "./TopN";
const panes = [
{ title: "Overview", component: Overview, key: "overview" },
{ title: "Advanced", component: Advanced, key: "advanced" },
+ { title: "TopN", component: TopN, key: "topn" },
{ title: "Nodes", component: Nodes, key: "nodes" },
{ title: "Indices", component: Indices, key: "indices" },
];
diff --git a/web/src/pages/Platform/Overview/components/TopN/ColorPicker.jsx b/web/src/pages/Platform/Overview/components/TopN/ColorPicker.jsx
new file mode 100644
index 00000000..65f10918
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/ColorPicker.jsx
@@ -0,0 +1,49 @@
+import { Button, Popover } from "antd";
+import styles from "./ColorPicker.less";
+import { SketchPicker } from 'react-color';
+import { useEffect, useRef, useState } from 'react';
+import { formatMessage } from "umi/locale";
+
+export default (props) => {
+ const { children, color, onChange, onRemove } = props;
+
+ const [currentColor, setCurrentColor] = useState();
+
+ const targetRef = useRef(null)
+
+ const onClose = () => {
+ targetRef?.current?.click()
+ }
+
+ useEffect(() => {
+ setCurrentColor(color)
+ }, [color])
+
+ return (
+
+
+ setCurrentColor(color.hex) }
+ />
+
+
+
+ { onRemove && }
+
+
+
+ )}>
+
+ {children}
+
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/ColorPicker.less b/web/src/pages/Platform/Overview/components/TopN/ColorPicker.less
new file mode 100644
index 00000000..3163e76f
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/ColorPicker.less
@@ -0,0 +1,14 @@
+.colorPicker {
+ padding: 0;
+ :global {
+ .ant-popover-inner-content {
+ padding: 0;
+ }
+ .ant-popover-arrow {
+ display: none;
+ }
+ .sketch-picker {
+ box-shadow: none !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/GradientColorPicker.jsx b/web/src/pages/Platform/Overview/components/TopN/GradientColorPicker.jsx
new file mode 100644
index 00000000..a3119c3b
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/GradientColorPicker.jsx
@@ -0,0 +1,55 @@
+import { Button, Popover } from "antd";
+import ColorPicker from "./ColorPicker";
+import styles from "./GradientColorPicker.less"
+import { useMemo } from "react";
+
+export default ({ value = [], onChange, style = {}, className = '' }) => {
+
+ const background = useMemo(() => {
+ if (value.length === 0) return undefined
+ if (value.length === 1) return value[0]
+ const each_percent = Math.round(100 / (value.length - 1))
+ return `linear-gradient(to right, ${value.map((item, index) => `${item} ${each_percent * index}%`).join(', ')})`
+ }, [JSON.stringify(value)])
+
+ const handleChange = (color, index) => {
+ const newValue = [...value];
+ newValue[index] = color
+ onChange(newValue)
+ }
+
+ const onAdd = (color) => {
+ const newValue = [...value];
+ newValue.push(color)
+ onChange(newValue)
+ }
+
+ const onRemove = (index) => {
+ const newValue = [...value];
+ newValue.splice(index, 1)
+ onChange(newValue)
+ }
+
+ return (
+
+ {
+ value.map((item, index) => (
+ onRemove(index)} onChange={(color) => handleChange(color, index)}>
+
+
+ ))
+ }
+
+
+
+
+ )}>
+
+
+ );
+};
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/GradientColorPicker.less b/web/src/pages/Platform/Overview/components/TopN/GradientColorPicker.less
new file mode 100644
index 00000000..ee1670d0
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/GradientColorPicker.less
@@ -0,0 +1,11 @@
+.colors {
+ :global {
+ .ant-popover-inner-content {
+ padding: 8px;
+ max-width: 136px;
+ }
+ .ant-popover-arrow {
+ display: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/Table.jsx b/web/src/pages/Platform/Overview/components/TopN/Table.jsx
new file mode 100644
index 00000000..97a05d75
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/Table.jsx
@@ -0,0 +1,60 @@
+import { getFormatter } from "@/utils/format";
+import { Treemap } from "@ant-design/charts";
+import { Table } from "antd";
+import { useMemo } from "react";
+import { formatMessage } from "umi/locale";
+
+export default (props) => {
+
+ const { type, config = {}, data = [] } = props
+ const {
+ top,
+ statisticArea,
+ statisticColor,
+ sourceArea,
+ sourceColor,
+ } = config;
+
+ const columns = useMemo(() => {
+ const newColumns = [{
+ title: formatMessage({ id: `cluster.monitor.${type}.title` }) ,
+ dataIndex: 'displayName',
+ key: 'displayName',
+ }];
+ if (sourceArea) {
+ const { format: formatArea, unit: unitArea } = sourceArea || {}
+ const formatterArea = getFormatter(formatArea)
+ newColumns.push({
+ title: unitArea ? `${sourceArea.name}(${unitArea})` : sourceArea.name,
+ dataIndex: 'value',
+ key: 'value',
+ defaultSortOrder: 'descend',
+ sorter: (a, b) => a['value'] - b['value'],
+ render: (value) => formatterArea ? formatterArea(value) : value
+ })
+ }
+ if (sourceColor) {
+ const { format: formatColor, unit: unitColor } = sourceColor
+ const formatterColor = getFormatter(formatColor)
+ newColumns.push({
+ title: unitColor ? `${sourceColor.name}(${unitColor})` : sourceColor.name,
+ dataIndex: 'valueColor',
+ key: 'valueColor',
+ sorter: (a, b) => a['valueColor'] - b['valueColor'],
+ render: (value) => formatterColor ? formatterColor(value) : value
+ })
+ }
+ return newColumns
+ }, [sourceArea, sourceColor])
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx b/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx
new file mode 100644
index 00000000..0bd51043
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx
@@ -0,0 +1,170 @@
+import { getFormatter } from "@/utils/format";
+import { Treemap } from "@ant-design/charts";
+import { Empty } from "antd";
+import { useMemo } from "react";
+
+const generateGradientColors = (startColor, endColor, steps) => {
+ function colorToRgb(color) {
+ const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
+ return rgb ? {
+ r: parseInt(rgb[1], 16),
+ g: parseInt(rgb[2], 16),
+ b: parseInt(rgb[3], 16)
+ } : null;
+ }
+
+ function rgbToHex(r, g, b) {
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
+ }
+
+ const startRGB = colorToRgb(startColor);
+ const endRGB = colorToRgb(endColor);
+ const diffR = endRGB.r - startRGB.r;
+ const diffG = endRGB.g - startRGB.g;
+ const diffB = endRGB.b - startRGB.b;
+
+ const colors = [];
+ for (let i = 0; i <= steps; i++) {
+ const r = startRGB.r + (diffR * i / steps);
+ const g = startRGB.g + (diffG * i / steps);
+ const b = startRGB.b + (diffB * i / steps);
+ colors.push(rgbToHex(Math.round(r), Math.round(g), Math.round(b)));
+ }
+
+ return colors;
+}
+
+const generateColors = (colors, data) => {
+ if (!colors || colors.length <= 1 || !data || data.length <= 1 || data.length <= colors.length) return colors
+ const gradientSize = data.length - colors.length
+ const steps = Math.floor(gradientSize / (colors.length - 1)) + 1
+ let remainder = gradientSize % (colors.length - 1)
+ const newColors = []
+ for(let i=0; i 0) {
+ fixSteps++
+ remainder--
+ }
+ const gradientColors = generateGradientColors(colors[i], colors[i+1], fixSteps)
+ newColors.push(...gradientColors.slice(0, gradientColors.length - 1))
+ }
+ newColors.push(colors[colors.length - 1])
+ return newColors
+}
+
+export default (props) => {
+
+ const { config = {}, data = [] } = props
+ const {
+ top,
+ colors = [],
+ sourceArea = {},
+ sourceColor = {},
+ } = config;
+
+ const color = useMemo(() => {
+ if (colors.length === 0 || !sourceColor?.key || data.length === 0) return undefined
+ const newColors = generateColors(colors, data)
+ const sortData = data.sort((a, b) => a.valueColor - b.valueColor)
+ return ({ name }) => {
+ const index = sortData.findIndex((item) => item.name === name)
+ if (index !== -1) {
+ return newColors[index] || newColors[0]
+ } else {
+ return newColors[0]
+ }
+ }
+ }, [data, colors, sourceColor])
+
+ return (
+
+ { data.length === 0 || data.some((item) => !Number.isFinite(item.value)) ? (
+
+
+
+ ) : (
+
{
+ const item = data.find((item) => item.name === text)
+ return item?.groupName || text
+ }
+ }
+ },
+ label: {
+ formatter: (item) => item.displayName
+ },
+ tooltip: {
+ customContent: (title, items) => {
+ if (!items[0]) return;
+ const { color, data } = items[0];
+ const { displayName, value, metricArea, nameArea, metricColor, nameColor, valueColor } = data;
+ const { format: formatArea, unit: unitArea } = sourceArea || {}
+ const formatterArea = getFormatter(formatArea)
+
+ const markers = [
+ {
+ name: nameArea,
+ value: formatterArea ? formatterArea(value) : value,
+ unit: unitArea,
+ marker:
+ }
+ ]
+
+ if (metricColor) {
+ const { format: formatColor, unit: unitColor } = sourceColor || {}
+ const formatterColor = getFormatter(formatColor)
+ markers.push({
+ name: nameColor,
+ value: formatterColor ? formatterColor(valueColor) : valueColor,
+ unit: unitColor,
+ marker:
+ })
+ }
+
+ return (
+
+ {
+
+ {displayName}
+
+ }
+
+ {
+ markers.map((item, index) => (
+
+ {item.marker}
+
+ {item.name}:
+
+ {item.unit ? `${item.value}${item.unit}` : item.value}
+
+
+
+ ))
+ }
+
+
+ );
+ },
+ }
+ }} />
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/index.jsx b/web/src/pages/Platform/Overview/components/TopN/index.jsx
new file mode 100644
index 00000000..88eb5d75
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/index.jsx
@@ -0,0 +1,523 @@
+
+import TreemapSvg from "@/components/Icons/Treemap"
+import styles from "./index.less"
+import { Button, Icon, Input, InputNumber, message, Popover, Radio, Select, Spin, Tooltip } from "antd";
+import { formatMessage } from "umi/locale";
+import ConvertSvg from "@/components/Icons/Convert"
+import { useEffect, useMemo, useRef, useState } from "react";
+import ColorPicker from "./ColorPicker";
+import Treemap from "./Treemap";
+import Table from "./Table";
+import GradientColorPicker from "./GradientColorPicker";
+import { cloneDeep } from "lodash";
+import request from "@/utils/request";
+import { formatTimeRange } from "@/lib/elasticsearch/util";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+
+export default (props) => {
+
+ const { type, clusterID, timeRange } = props;
+
+ const [currentMode, setCurrentMode] = useState('treemap')
+
+ const [metrics, setMetrics] = useState([])
+
+ const [formData, setFormData] = useState({
+ top: 15,
+ colors: ['#00bb1b', '#fcca00', '#ff4d4f']
+ })
+
+ const [config, setConfig] = useState({})
+
+ const [loading, setLoading] = useState(false)
+ const [data, setData] = useState([])
+ const [result, setResult] = useState()
+ const searchParamsRef = useRef()
+
+ const fetchMetrics = async (type) => {
+ setLoading(true)
+ const res = await request(`/collection/metric/_search`, {
+ method: 'POST',
+ body: {
+ size: 10000,
+ from: 0,
+ query: { bool: { filter: [{ "term": { "level": type === 'index' ? 'indices' : type } }] }}
+ }
+ })
+ if (res?.hits?.hits) {
+ const newMetrics = res?.hits?.hits.filter((item) => {
+ const { items = [] } = item._source;
+ if (items.length === 0) return false
+ return true;
+ }).map((item) => ({ ...item._source }))
+ setMetrics(newMetrics)
+ }
+ setLoading(false)
+ }
+
+ const fetchData = async (type, clusterID, timeRange, formData, shouldLoading = true) => {
+ if (!clusterID || !timeRange || !formData.sourceArea) return;
+ if (shouldLoading) {
+ setLoading(true)
+ }
+ const { top, sourceArea = {}, statisticArea, statisticColor, sourceColor = {} } = formData
+ const newTimeRange = formatTimeRange(timeRange);
+ searchParamsRef.current = { type, clusterID, formData }
+ const body = {
+ "index_pattern": ".infini_metrics*",
+ "time_field": "timestamp",
+ "bucket_size": "auto",
+ "filter": {
+ "bool": {
+ "must": [{
+ "term": {
+ "metadata.name": {
+ "value": `${type}_stats`
+ }
+ }
+ }, {
+ "term": {
+ "metadata.category": {
+ "value": "elasticsearch"
+ }
+ }
+ }, {
+ "range": {
+ "timestamp": {
+ "gte": newTimeRange.min,
+ "lte": newTimeRange.max,
+ }
+ }
+ }],
+ "must_not": type === 'index' ? [{
+ "term": {
+ "metadata.labels.index_name": {
+ "value": `_all`
+ }
+ }
+ }] : [],
+ "filter": [
+ {
+ "term": { "metadata.labels.cluster_id": clusterID }
+ }
+ ],
+ }
+ },
+ "formulas": [sourceArea?.formula, sourceColor?.formula].filter((item) => !!item),
+ "items": [...(sourceArea?.items || [])?.map((item) => {
+ item.statistic = statisticArea
+ if (item.statistic === 'rate') {
+ item.statistic = 'derivative'
+ }
+ return item
+ }),...(sourceColor?.items || [])?.map((item) => {
+ item.statistic = statisticColor
+ if (item.statistic === 'rate') {
+ item.statistic = 'derivative'
+ }
+ return item
+ })].filter((item) => !!item),
+ "groups": [{
+ "field": type === 'shard' ? `metadata.labels.shard_id` : `metadata.labels.${type}_name`,
+ "limit": top
+ }],
+ "sort": [{
+ "direction": "desc",
+ "key": sourceArea?.items[0].name
+ }]
+ }
+ if (statisticArea !== 'rate' && statisticColor !== 'rate') {
+ delete body['time_field']
+ delete body['bucket_size']
+ }
+ const res = await request(`/elasticsearch/infini_default_system_cluster/visualization/data`, {
+ method: 'POST',
+ body
+ })
+ if (res && !res.error) {
+ setResult(res)
+ setConfig(cloneDeep(formData))
+ } else {
+ setResult()
+ }
+ if (shouldLoading) {
+ setLoading(false)
+ }
+ }
+
+ const onFormDataChange = (values) => {
+ setFormData({
+ ...cloneDeep(formData),
+ ...values
+ })
+ }
+
+ const onMetricExchange = () => {
+ const newFormData = cloneDeep(formData);
+ const sourceTmp = cloneDeep(newFormData.sourceArea)
+ const statisticTmp = newFormData.statisticArea
+ newFormData.sourceArea = cloneDeep(newFormData.sourceColor)
+ newFormData.statisticArea = newFormData.statisticColor
+ newFormData.sourceColor = sourceTmp
+ newFormData.statisticColor = statisticTmp
+ setResult()
+ setFormData(newFormData)
+ fetchData(type, clusterID, timeRange, newFormData)
+ }
+
+ useEffect(() => {
+ fetchMetrics(type)
+ }, [type])
+
+ const isTreemap = useMemo(() => {
+ return currentMode === 'treemap'
+ }, [currentMode])
+
+ const { sourceArea, sourceColor } = config
+
+ const formatData = useMemo(() => {
+
+ const { data = [] } = result || {};
+ if (!data || data.length === 0 || !sourceArea) return []
+ return data.filter((item) => !!(item.groups && item.groups[0])).map((item) => {
+ const { groups = [], value } = item;
+ let name = groups[0];
+ if (type === 'shard') {
+ const splits = name.split(':')
+ if (splits.length > 1) {
+ name = splits.slice(1).join(':')
+ }
+ }
+ const object = {
+ name: name,
+ displayName: name,
+ value: value?.[sourceArea.formula] || 0,
+ metricArea: sourceArea.key,
+ nameArea: sourceArea.name,
+ }
+ if (sourceColor?.formula) {
+ object['metricColor'] = sourceColor.key
+ object['valueColor'] = value?.[sourceColor.formula] || 0
+ object['nameColor'] = sourceColor.name
+ }
+ return object
+ })
+ }, [result, sourceArea, sourceColor, type])
+
+ useEffect(() => {
+ if (searchParamsRef.current) {
+ const { type, clusterID, formData } = searchParamsRef.current
+ fetchData(type, clusterID, timeRange, formData, false)
+ }
+ }, [timeRange])
+
+ return (
+
+
+
+
+ setCurrentMode(e.target.value)}
+ className={styles.mode}
+ style={{ marginRight: 12, marginBottom: 12 }}
+ >
+
+
+
+
+
+
+
+
+ onFormDataChange({ top: value })}
+ />
+
+
+
+
+
+
+
+
+
+ {
+ onFormDataChange({ colors: value })
+ setConfig({
+ ...cloneDeep(config),
+ colors: value
+ })
+ }}/>
+
+
+ {/* setCurrentMode(e.target.value)}
+ className={styles.mode}
+ >
+
+
+
+
+
+
+
+
+
+ onFormDataChange({ top: value })}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ onFormDataChange({ colors: value })
+ setConfig({
+ ...cloneDeep(config),
+ colors: value
+ })
+ }}/>
+
+
+ */}
+
+
+
+ {
+ result?.request && (
+
+
+ message.success(formatMessage({id: "cluster.metrics.request.copy.success"}))}>
+
+
+
+
+ )
+ }
+ { isTreemap ?
:
}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/index.less b/web/src/pages/Platform/Overview/components/TopN/index.less
new file mode 100644
index 00000000..f00b1aa7
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/index.less
@@ -0,0 +1,78 @@
+.topn {
+ .header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+
+ :global {
+ .ant-input-group {
+ display: flex !important;
+ flex-wrap: wrap !important;
+ }
+ }
+
+ .mode {
+ width: 84px;
+ :global {
+ .ant-radio-button-wrapper {
+ width: 42px;
+ padding: 0 13px;
+ }
+ }
+ }
+ }
+
+ .content {
+ width: 100%;
+ height: calc(100vh - 500px);
+ min-height: 500px;
+ position: relative;
+
+ .info {
+ position: absolute;
+ display: none;
+ right: 2px;
+ top: 2px;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ line-height: 24px;
+ text-align: center;
+ font-size: 16px;
+ box-shadow: rgba(0, 0, 0, 0.16) 0px 0px 5px 0px;
+ background: #fff;
+ z-index: 11;
+ cursor: pointer;
+ color: rgba(0, 0, 0, 0.45);
+ transition: all 0.3s ease;
+
+ &:hover {
+ color: #1890ff;
+ box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 5px 0px;
+ }
+ }
+
+ &:hover {
+ .info {
+ display: block;
+ }
+ }
+ }
+}
+
+.borderRadiusLeft {
+ border-top-left-radius: 4px !important;
+ border-bottom-left-radius: 4px !important;
+}
+
+.borderRadiusRight {
+ border-top-right-radius: 4px !important;
+ border-bottom-right-radius: 4px !important;
+ :global {
+ .ant-select-selection {
+ border-top-right-radius: 4px !important;
+ border-bottom-right-radius: 4px !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/pages/System/Cluster/models/cluster.js b/web/src/pages/System/Cluster/models/cluster.js
index 1ed2ec8b..c31bbdd9 100644
--- a/web/src/pages/System/Cluster/models/cluster.js
+++ b/web/src/pages/System/Cluster/models/cluster.js
@@ -109,15 +109,14 @@ export default {
},
});
}
-
+
//handle global cluster logic
yield put({
type: "global/updateCluster",
payload: {
+ ...payload,
id: res._id,
- name: payload.name,
- monitored: payload.monitored,
},
});
return res;