diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md
index 2d0f74bc..045433f1 100644
--- a/docs/content.en/docs/release-notes/_index.md
+++ b/docs/content.en/docs/release-notes/_index.md
@@ -21,6 +21,7 @@ Information about release notes of INFINI Console is provided here.
- Optimize UI of agent list when its columns are overflow.
- Add loading to each row in overview table.
- Adapter metrics query with cluster id and cluster uuid
+- Add suggestion to chart in monitor if is no data because the time interval is less than the collection interval.
## 1.27.0 (2024-12-09)
diff --git a/web/src/components/Overview/Detail/Metrics/MetricLineList.jsx b/web/src/components/Overview/Detail/Metrics/MetricLineList.jsx
index 756f55d9..77456903 100644
--- a/web/src/components/Overview/Detail/Metrics/MetricLineList.jsx
+++ b/web/src/components/Overview/Detail/Metrics/MetricLineList.jsx
@@ -8,7 +8,7 @@ import request from "@/utils/request";
import MetricChart from "@/pages/Platform/Overview/components/MetricChart";
export default (props) => {
- const { action, timeRange, timezone, timeout, overview, renderExtraMetric, metrics = [], queryParams } = props
+ const { action, timeRange, timezone, timeout, overview, renderExtraMetric, metrics = [], queryParams, handleTimeIntervalChange } = props
return (
@@ -60,6 +60,7 @@ export default (props) => {
};
return
}}
+ handleTimeIntervalChange={handleTimeIntervalChange}
/>
)
})}
diff --git a/web/src/components/Overview/Detail/Metrics/index.js b/web/src/components/Overview/Detail/Metrics/index.js
index 64877c0a..610c3603 100644
--- a/web/src/components/Overview/Detail/Metrics/index.js
+++ b/web/src/components/Overview/Detail/Metrics/index.js
@@ -1,4 +1,4 @@
-import React, { useState, useMemo } from "react";
+import React, { useState, useMemo, useEffect } from "react";
import { Tabs, Button } from "antd";
import { ESPrefix } from "@/services/common";
import useFetch from "@/lib/hooks/use_fetch";
@@ -15,7 +15,7 @@ import { formatMessage } from "umi/locale";
import DatePicker from "@/common/src/DatePicker";
import { getLocale } from "umi/locale";
import { getTimezone } from "@/utils/utils";
-import { TIMEOUT_CACHE_KEY } from "../../Monitor";
+import { getAllTimeSettingsCache, TIME_SETTINGS_KEY } from "../../Monitor";
const { TabPane } = Tabs;
@@ -30,6 +30,8 @@ export default (props) => {
metrics = [],
} = props;
+ const allTimeSettingsCache = getAllTimeSettingsCache() || {}
+
const [spinning, setSpinning] = useState(false);
const [state, setState] = useState({
timeRange: {
@@ -37,12 +39,12 @@ export default (props) => {
max: "now",
timeFormatter: formatter.dates(1),
},
- timeInterval: '',
- timeout: localStorage.getItem(TIMEOUT_CACHE_KEY) || '120s',
+ timeInterval: allTimeSettingsCache.timeInterval,
+ timeout: allTimeSettingsCache.timeout || '120s',
});
- const [refresh, setRefresh] = useState({ isRefreshPaused: false });
- const [timeZone, setTimeZone] = useState(() => getTimezone());
+ const [refresh, setRefresh] = useState({ isRefreshPaused: allTimeSettingsCache.isRefreshPaused || false, refreshInterval: allTimeSettingsCache.refreshInterval || 30000 });
+ const [timeZone, setTimeZone] = useState(() => allTimeSettingsCache.timeZone || getTimezone());
const handleTimeChange = ({ start, end, timeInterval, timeout }) => {
const bounds = calculateBounds({
@@ -65,6 +67,15 @@ export default (props) => {
setSpinning(true);
};
+ const onTimeSettingsChange = (timeSettings) => {
+ let allTimeSettings = getAllTimeSettingsCache();
+ allTimeSettings = {
+ ...(allTimeSettings || {}),
+ ...(timeSettings || {})
+ }
+ localStorage.setItem(TIME_SETTINGS_KEY, JSON.stringify(allTimeSettings))
+ }
+
const [linkMoreNew] = useMemo(() => {
let urlObj = parseUrl(linkMore);
let query = urlObj.query;
@@ -94,14 +105,21 @@ export default (props) => {
end={state.timeRange.max}
onRangeChange={handleTimeChange}
{...refresh}
- onRefreshChange={setRefresh}
+ onRefreshChange={(newRefresh) => {
+ onTimeSettingsChange(newRefresh)
+ setRefresh(newRefresh)
+ }}
onRefresh={handleTimeChange}
showTimeSetting={true}
showTimeInterval={true}
showTimeout={true}
timeout={state.timeout}
+ timeInterval={state.timeInterval}
onTimeSettingChange={(timeSetting) => {
- localStorage.setItem(TIMEOUT_CACHE_KEY, timeSetting.timeout)
+ onTimeSettingsChange({
+ timeInterval: timeSetting.timeInterval,
+ timeout: timeSetting.timeout
+ })
setState({
...state,
timeInterval: timeSetting.timeInterval,
@@ -109,7 +127,12 @@ export default (props) => {
});
}}
timeZone={timeZone}
- onTimeZoneChange={setTimeZone}
+ onTimeZoneChange={(timeZone) => {
+ onTimeSettingsChange({
+ timeZone,
+ })
+ setTimeZone(timeZone)
+ }}
recentlyUsedRangesKey={'overview-detail'}
/>
@@ -124,6 +147,15 @@ export default (props) => {
renderExtraMetric={renderExtraMetric}
metrics={metrics}
{...state}
+ handleTimeIntervalChange={(timeInterval) => {
+ onTimeSettingsChange({
+ timeInterval,
+ })
+ setState({
+ ...state,
+ timeInterval,
+ });
+ }}
bucketSize={state.timeInterval}
/>
diff --git a/web/src/components/Overview/Detail/Metrics/index.scss b/web/src/components/Overview/Detail/Metrics/index.scss
index a9e28e4a..6cd527c3 100644
--- a/web/src/components/Overview/Detail/Metrics/index.scss
+++ b/web/src/components/Overview/Detail/Metrics/index.scss
@@ -32,7 +32,7 @@
flex-wrap: wrap;
justify-content: space-between;
.lineWrapper {
- height: 150px;
+ height: 180px;
width: 48.8%;
border: 1px solid #e8e8e8;
border-radius: 2px;
@@ -45,7 +45,7 @@
white-space: nowrap;
}
.chartBody {
- height: 120px;
+ height: 150px;
padding: 0 2px 2px 2px;
}
}
diff --git a/web/src/components/Overview/Monitor/index.jsx b/web/src/components/Overview/Monitor/index.jsx
index 9b294290..e319410a 100644
--- a/web/src/components/Overview/Monitor/index.jsx
+++ b/web/src/components/Overview/Monitor/index.jsx
@@ -41,7 +41,17 @@ const formatTimeout = (timeout) => {
return timeout
}
-export const TIMEOUT_CACHE_KEY = "monitor-timeout"
+export const TIME_SETTINGS_KEY = "monitor-time-settings"
+
+export const getAllTimeSettingsCache = () => {
+ const allTimeSettings = localStorage.getItem(TIME_SETTINGS_KEY) || `{}`
+ try {
+ const object = JSON.parse(allTimeSettings);
+ return object || {}
+ } catch (error) {
+ return {}
+ }
+}
const Monitor = (props) => {
const {
@@ -54,6 +64,8 @@ const Monitor = (props) => {
checkPaneParams,
} = props;
+ const allTimeSettingsCache = getAllTimeSettingsCache()
+
const [param, setParam] = useQueryParam("_g", JsonParam);
const [spinning, setSpinning] = useState(false);
@@ -65,15 +77,15 @@ const Monitor = (props) => {
max: param?.timeRange?.max || "now",
timeFormatter: formatter.dates(1),
},
- timeInterval: formatTimeInterval(param?.timeInterval),
- timeout: formatTimeout(param?.timeout) || localStorage.getItem(TIMEOUT_CACHE_KEY) || '120s',
+ timeInterval: formatTimeInterval(param?.timeInterval) || allTimeSettingsCache.timeInterval,
+ timeout: formatTimeout(param?.timeout) || allTimeSettingsCache.timeout || '120s',
param: param,
- refresh: true
+ refresh: true,
})
);
- const [refresh, setRefresh] = useState({ isRefreshPaused: false, refreshInterval: 30000 });
- const [timeZone, setTimeZone] = useState(() => getTimezone());
+ const [refresh, setRefresh] = useState({ isRefreshPaused: allTimeSettingsCache.isRefreshPaused || false, refreshInterval: allTimeSettingsCache.refreshInterval || 30000 });
+ const [timeZone, setTimeZone] = useState(() => allTimeSettingsCache.timeZone || getTimezone());
useEffect(() => {
setParam({ ...param, timeRange: state.timeRange, timeInterval: state.timeInterval, timeout: state.timeout });
@@ -108,6 +120,15 @@ const Monitor = (props) => {
});
};
+ const onTimeSettingsChange = (timeSettings) => {
+ let allTimeSettings = getAllTimeSettingsCache();
+ allTimeSettings = {
+ ...(allTimeSettings || {}),
+ ...(timeSettings || {})
+ }
+ localStorage.setItem(TIME_SETTINGS_KEY, JSON.stringify(allTimeSettings))
+ }
+
const breadcrumbList = getBreadcrumbList(state);
const isAgent = useMemo(() => {
@@ -133,7 +154,10 @@ const Monitor = (props) => {
handleTimeChange({ start, end })
}}
{...refresh}
- onRefreshChange={setRefresh}
+ onRefreshChange={(newRefresh) => {
+ onTimeSettingsChange(newRefresh)
+ setRefresh(newRefresh)
+ }}
onRefresh={handleTimeChange}
showTimeSetting={true}
showTimeInterval={true}
@@ -141,7 +165,10 @@ const Monitor = (props) => {
showTimeout={true}
timeout={state.timeout}
onTimeSettingChange={(timeSetting) => {
- localStorage.setItem(TIMEOUT_CACHE_KEY, timeSetting.timeout)
+ onTimeSettingsChange({
+ timeInterval: timeSetting.timeInterval,
+ timeout: timeSetting.timeout
+ })
setState({
...state,
timeInterval: timeSetting.timeInterval,
@@ -149,7 +176,12 @@ const Monitor = (props) => {
});
}}
timeZone={timeZone}
- onTimeZoneChange={setTimeZone}
+ onTimeZoneChange={(timeZone) => {
+ onTimeSettingsChange({
+ timeZone,
+ })
+ setTimeZone(timeZone)
+ }}
recentlyUsedRangesKey={'monitor'}
/>
@@ -188,6 +220,15 @@ const Monitor = (props) => {
isAgent={isAgent}
{...state}
handleTimeChange={handleTimeChange}
+ handleTimeIntervalChange={(timeInterval) => {
+ onTimeSettingsChange({
+ timeInterval,
+ })
+ setState({
+ ...state,
+ timeInterval,
+ });
+ }}
setSpinning={setSpinning}
{...extraParams}
bucketSize={state.timeInterval}
diff --git a/web/src/components/Overview/index.tsx b/web/src/components/Overview/index.tsx
index 59f6d33a..7cb54a5f 100644
--- a/web/src/components/Overview/index.tsx
+++ b/web/src/components/Overview/index.tsx
@@ -274,7 +274,7 @@ export default forwardRef((props: IProps, ref: any) => {
{
setSelectedItem(item);
drawRef.current?.open();
diff --git a/web/src/locales/en-US/cluster.js b/web/src/locales/en-US/cluster.js
index 280f8ae7..88bffd9b 100644
--- a/web/src/locales/en-US/cluster.js
+++ b/web/src/locales/en-US/cluster.js
@@ -89,6 +89,7 @@ export default {
"cluster.monitor.node.title": "Node",
"cluster.monitor.index.title": "Index",
"cluster.monitor.queue.title": "Thread Pool",
+ "cluster.monitor.shard.title": "Shard",
"cluster.monitor.summary.name": "Cluster Name",
"cluster.monitor.summary.online_time": "Uptime",
"cluster.monitor.summary.version": "Version",
@@ -352,6 +353,12 @@ export default {
"cluster.metrics.request.copy": "Copy request",
"cluster.metrics.request.copy.success": "Copy request successfully",
+ "cluster.metrics.time_interval.reload": "Apply global time interval({time_interval})",
+ "cluster.metrics.time_interval.set.global": "Global",
+ "cluster.metrics.time_interval.set.current": "Current",
+ "cluster.metrics.time_interval.empty": "No data, the time interval is less than the collection interval, suggest to set the time interval to {min_bucket_size} seconds.",
+ "cluster.metrics.time_interval.apply": "Apply suggestion",
+
"cluster.collect.last_active_at": "Last Active At",
};
diff --git a/web/src/locales/zh-CN/cluster.js b/web/src/locales/zh-CN/cluster.js
index 37f67338..c22e9a72 100644
--- a/web/src/locales/zh-CN/cluster.js
+++ b/web/src/locales/zh-CN/cluster.js
@@ -80,6 +80,7 @@ export default {
"cluster.monitor.node.title": "节点",
"cluster.monitor.index.title": "索引",
"cluster.monitor.queue.title": "线程池",
+ "cluster.monitor.shard.title": "分片",
"cluster.monitor.summary.name": "集群名称",
"cluster.monitor.summary.online_time": "在线时长",
"cluster.monitor.summary.version": "集群版本",
@@ -337,6 +338,12 @@ export default {
"cluster.metrics.request.copy": "复制请求",
"cluster.metrics.request.copy.success": "复制请求成功",
+ "cluster.metrics.time_interval.reload": "设置为全局时间间隔({time_interval})",
+ "cluster.metrics.time_interval.set.global": "全局",
+ "cluster.metrics.time_interval.set.current": "当前",
+ "cluster.metrics.time_interval.empty": "暂无数据,当前时间间隔小于采集间隔,建议设置时间间隔为{min_bucket_size}秒",
+ "cluster.metrics.time_interval.apply": "应用建议",
+
"cluster.collect.last_active_at": "最后活动时间",
};
diff --git a/web/src/pages/Platform/Overview/Cluster/Monitor/advanced.jsx b/web/src/pages/Platform/Overview/Cluster/Monitor/advanced.jsx
index f7a3746f..16d1a03b 100644
--- a/web/src/pages/Platform/Overview/Cluster/Monitor/advanced.jsx
+++ b/web/src/pages/Platform/Overview/Cluster/Monitor/advanced.jsx
@@ -26,26 +26,9 @@ export const isVersionGTE6 = (cluster) => {
return false
}
-export default ({
- selectedCluster,
- clusterID,
- timeRange,
- handleTimeChange,
- timezone,
- bucketSize,
- timeout,
- refresh,
-}) => {
+export default (props) => {
- const tabProps = {
- clusterID,
- timeRange,
- handleTimeChange,
- timezone,
- bucketSize,
- timeout,
- refresh
- }
+ const { selectedCluster, clusterID } = props
const isVersionGTE8_6 = useMemo(() => {
return shouldHaveModelInferenceBreaker(selectedCluster)
@@ -83,7 +66,7 @@ export default ({
})}
>
{
+export default (props) => {
+
+ const { clusterID } = props
+
return (
);
diff --git a/web/src/pages/Platform/Overview/Indices/Monitor/advanced.jsx b/web/src/pages/Platform/Overview/Indices/Monitor/advanced.jsx
index bc73ee74..365a2369 100644
--- a/web/src/pages/Platform/Overview/Indices/Monitor/advanced.jsx
+++ b/web/src/pages/Platform/Overview/Indices/Monitor/advanced.jsx
@@ -2,33 +2,19 @@ import { useState } from "react";
import StatisticBar from "./statistic_bar";
import IndexMetric from "../../components/index_metric";
-export default ({
- clusterID,
- indexName,
- timeRange,
- handleTimeChange,
- shardID,
- bucketSize,
- timezone,
- timeout,
- refresh,
-}) => {
+export default (props) => {
+
+ const { indexName } = props
+
const [param, setParam] = useState({
show_top: false,
index_name: indexName,
});
return (
{
+export default (props) => {
+
+ const {
+ isAgent,
+ clusterID,
+ indexName,
+ shardID,
+ } = props
+
let url = `${ESPrefix}/${clusterID}/index/${indexName}/metrics`;
if(shardID){
url += `?shard_id=${shardID}`
}
return (
{
+export default (props) => {
- const tabProps = {
+ const {
+ selectedCluster,
clusterID,
- timeRange,
- handleTimeChange,
- timezone,
- bucketSize,
- timeout,
- refresh
- }
+ nodeID,
+ } = props
const isVersionGTE8_6 = useMemo(() => {
return shouldHaveModelInferenceBreaker(selectedCluster)
@@ -69,7 +55,7 @@ export default ({
})}
>
{
+export default (props) => {
+
+ const {
+ isAgent,
+ clusterID,
+ nodeID,
+ } = props
return (
{
height = 200,
customRenderChart,
instance,
- pointerUpdate
+ pointerUpdate,
+ handleTimeIntervalChange
} = props;
const [loading, setLoading] = useState(false)
@@ -55,20 +56,26 @@ export default (props) => {
const containerRef = useRef(null)
const firstFetchRef = useRef(true)
+
+ const [timeInterval, setTimeInterval] = useState()
- const fetchData = async (queryParams, fetchUrl, metricKey, showLoading) => {
+ const fetchData = async (queryParams, fetchUrl, metricKey, timeInterval, showLoading) => {
if (!observerRef.current.isInView || !fetchUrl) return;
setError()
if (firstFetchRef.current || showLoading) {
setLoading(true)
}
+ const newQueryParams = {
+ ...queryParams,
+ key: metricKey,
+ timeout
+ }
+ if (timeInterval) {
+ newQueryParams.bucket_size = timeInterval
+ }
const res = await request(fetchUrl, {
method: 'GET',
- queryParams: {
- ...queryParams,
- key: metricKey,
- timeout
- },
+ queryParams: newQueryParams,
ignoreTimeout: true
}, false, false)
if (res?.error) {
@@ -86,9 +93,9 @@ export default (props) => {
}
useEffect(() => {
- observerRef.current.deps = cloneDeep([queryParams, fetchUrl, metricKey, refresh])
- fetchData(queryParams, fetchUrl, metricKey, refresh)
- }, [JSON.stringify(queryParams), fetchUrl, metricKey, refresh])
+ observerRef.current.deps = cloneDeep([queryParams, fetchUrl, metricKey, timeInterval, refresh])
+ fetchData(queryParams, fetchUrl, metricKey, timeInterval, refresh)
+ }, [JSON.stringify(queryParams), fetchUrl, metricKey, timeInterval, refresh])
useEffect(() => {
const observer = new IntersectionObserver(
@@ -150,9 +157,33 @@ export default (props) => {
const axis = metric?.axis || [];
const lines = metric?.lines || [];
if (lines.every((item) => !item.data || item.data.length === 0)) {
+ const emptyProps = {}
+ if (metric?.min_bucket_size > 0 && metric?.hits_total > 0) {
+ emptyProps.description = (
+ <>
+
+ {formatMessage({ id: "cluster.metrics.time_interval.empty" }, { min_bucket_size: metric.min_bucket_size})}
+
+
+ handleTimeIntervalChange(`${metric?.min_bucket_size}s`)}>
+ {formatMessage({ id: `cluster.metrics.time_interval.set.global`})}
+
+ setTimeInterval(`${metric?.min_bucket_size}s`)}>
+ {formatMessage({ id: `cluster.metrics.time_interval.set.current`})}
+
+
+ )}>
+ e.preventDefault()}>
+ {formatMessage({ id: `cluster.metrics.time_interval.apply`})}
+
+
+ >
+ )
+ }
return (
-
+
)
}
@@ -297,12 +328,20 @@ export default (props) => {
{
+ {
+ timeInterval && (
+
+ setTimeInterval()}/>
+
+ )
+ }
{
metric?.request && (
message.success(formatMessage({id: "cluster.metrics.request.copy.success"}))}
/>
@@ -311,7 +350,7 @@ export default (props) => {
)
}
- fetchData(...observerRef.current.deps, true)}/>
+ fetchData(...observerRef.current.deps, true)}/>
}
diff --git a/web/src/pages/Platform/Overview/components/cluster_metric.jsx b/web/src/pages/Platform/Overview/components/cluster_metric.jsx
index 6b25e5df..951ceb6e 100644
--- a/web/src/pages/Platform/Overview/components/cluster_metric.jsx
+++ b/web/src/pages/Platform/Overview/components/cluster_metric.jsx
@@ -8,7 +8,7 @@ import { formatTimeRange } from "@/lib/elasticsearch/util";
export default (props) => {
- const { fetchUrl, overview, metrics = [], renderExtra, timeRange, timeout, timezone, refresh, bucketSize, handleTimeChange } = props
+ const { fetchUrl, overview, metrics = [], renderExtra, timeRange, timeout, timezone, refresh, bucketSize, handleTimeChange, handleTimeIntervalChange } = props
if (!fetchUrl || metrics.length === 0) {
return null;
@@ -81,6 +81,7 @@ export default (props) => {
}
return metric
}}
+ handleTimeIntervalChange={handleTimeIntervalChange}
/>
))}
{
diff --git a/web/src/pages/Platform/Overview/components/index_metric.jsx b/web/src/pages/Platform/Overview/components/index_metric.jsx
index 17d4ced5..30a26e67 100644
--- a/web/src/pages/Platform/Overview/components/index_metric.jsx
+++ b/web/src/pages/Platform/Overview/components/index_metric.jsx
@@ -25,6 +25,7 @@ export default (props) => {
metrics = [],
timeout,
refresh,
+ handleTimeIntervalChange
} = props
if (!clusterID || metrics.length == 0) {
@@ -158,6 +159,7 @@ export default (props) => {
className={"metric-item"}
timeout={timeout}
refresh={refresh}
+ handleTimeIntervalChange={handleTimeIntervalChange}
/>
))
}
diff --git a/web/src/pages/Platform/Overview/components/node_metric.jsx b/web/src/pages/Platform/Overview/components/node_metric.jsx
index ceaa8d40..92130fe0 100644
--- a/web/src/pages/Platform/Overview/components/node_metric.jsx
+++ b/web/src/pages/Platform/Overview/components/node_metric.jsx
@@ -22,7 +22,8 @@ export default (props) => {
bucketSize,
timeout,
refresh,
- metrics = []
+ metrics = [],
+ handleTimeIntervalChange
} = props
if (!clusterID || metrics.length == 0) {
@@ -181,6 +182,7 @@ export default (props) => {
className={"metric-item"}
timeout={timeout}
refresh={refresh}
+ handleTimeIntervalChange={handleTimeIntervalChange}
/>
))
}
diff --git a/web/src/pages/Platform/Overview/components/queue_metric.jsx b/web/src/pages/Platform/Overview/components/queue_metric.jsx
index 0f58e69c..b819455d 100644
--- a/web/src/pages/Platform/Overview/components/queue_metric.jsx
+++ b/web/src/pages/Platform/Overview/components/queue_metric.jsx
@@ -22,7 +22,8 @@ export default (props) => {
bucketSize,
metrics = [],
timeout,
- refresh
+ refresh,
+ handleTimeIntervalChange
} = props
if (!clusterID || metrics.length == 0) {
@@ -181,6 +182,7 @@ export default (props) => {
className={"metric-item"}
timeout={timeout}
refresh={refresh}
+ handleTimeIntervalChange={handleTimeIntervalChange}
/>
))
}