diff --git a/.gitignore b/.gitignore index 2caf52a70..9589496e7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ public/bound .worktrees/ coverage/ .playwright-mcp/ +docs/ +midscene_run/ diff --git a/src/components/PromGraphCpt/Graph.tsx b/src/components/PromGraphCpt/Graph.tsx index aff9ca1fd..25795dda0 100644 --- a/src/components/PromGraphCpt/Graph.tsx +++ b/src/components/PromGraphCpt/Graph.tsx @@ -14,7 +14,7 @@ * limitations under the License. * */ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect, useContext, useRef, useCallback } from 'react'; import moment from 'moment'; import _ from 'lodash'; import { Space, InputNumber, Radio, Button, Popover, Tooltip } from 'antd'; @@ -57,6 +57,7 @@ interface IProps { graphStandardOptionsPlacement?: TooltipPlacement; defaultUnit?: string; panelWidth?: number; // 用于 Graph 组件的宽度计算 + refetchOnZoom?: boolean; } enum ChartType { @@ -100,8 +101,17 @@ export default function Graph(props: IProps) { graphStandardOptionsPlacement = 'left', defaultUnit, panelWidth, + refetchOnZoom = false, } = props; const [data, setData] = useState([]); + const [zoomRangeState, setZoomRangeState] = useState<{ range: IRawTimeRange; sourceKey: string }>(); + const [resetZoomVersion, setResetZoomVersion] = useState(0); + const querySeqRef = useRef(0); + const rangeKey = JSON.stringify(range); + const zoomSourceKey = JSON.stringify({ datasourceValue, promql, range }); + const activeZoomRange = refetchOnZoom && zoomRangeState?.sourceKey === zoomSourceKey ? zoomRangeState.range : undefined; + const effectiveRange = activeZoomRange || range; + const effectiveRangeKey = JSON.stringify(effectiveRange); const [highLevelConfig, setHighLevelConfig] = useState({ shared: false, sharedSortDirection: 'desc', @@ -144,9 +154,35 @@ export default function Graph(props: IProps) { } }, [defaultUnit]); + useEffect(() => { + if (refetchOnZoom) { + setZoomRangeState(undefined); + setResetZoomVersion((v) => v + 1); + } + }, [refetchOnZoom, zoomSourceKey]); + + const handleZoomWithoutDefault = useCallback( + (times?: Date[]) => { + if (!refetchOnZoom) return; + if (times?.length === 2) { + setZoomRangeState({ + range: { + start: moment(times[0]), + end: moment(times[1]), + }, + sourceKey: zoomSourceKey, + }); + } else { + setZoomRangeState(undefined); + setResetZoomVersion((v) => v + 1); + } + }, + [refetchOnZoom, zoomSourceKey], + ); + useEffect(() => { if (datasourceValue && promql) { - const parsedRange = parseRange(range); + const parsedRange = parseRange(effectiveRange); const start = moment(parsedRange.start).unix(); const end = moment(parsedRange.end).unix(); const realStep = getRealStep({ @@ -156,11 +192,12 @@ export default function Graph(props: IProps) { toUnix: end, }); const queryStart = Date.now(); + const querySeq = ++querySeqRef.current; setLoading(true); getPromData(`${url}/${datasourceValue}/api/v1/query_range`, { query: interpolateString({ query: promql, - range, + range: effectiveRange, minStep, maxDataPoints: maxDataPoints || panelWidth, }), @@ -169,6 +206,7 @@ export default function Graph(props: IProps) { step: realStep, }) .then((res) => { + if (querySeq !== querySeqRef.current) return; const series = _.map(res?.result, (item) => { return { id: _.uniqueId('series_'), @@ -187,17 +225,19 @@ export default function Graph(props: IProps) { setErrorContent(''); }) .catch((err) => { + if (querySeq !== querySeqRef.current) return; const msg = _.get(err, 'message'); setErrorContent(`Error executing query: ${msg}`); }) .finally(() => { + if (querySeq !== querySeqRef.current) return; setLoading(false); }); } - }, [JSON.stringify(range), minStep, maxDataPoints, datasourceValue, promql, refreshFlag]); + }, [effectiveRangeKey, minStep, maxDataPoints, datasourceValue, promql, refreshFlag]); return ( -
+
@@ -294,7 +334,7 @@ export default function Graph(props: IProps) { version: DASHBOARD_VERSION, name: promql, step: minStep, - range, + range: effectiveRange, ...lineGraphProps, targets: [ { @@ -336,7 +376,15 @@ export default function Graph(props: IProps) { )}
- +
); } diff --git a/src/components/PromGraphCpt/index.tsx b/src/components/PromGraphCpt/index.tsx index e65825a02..2ca09e257 100644 --- a/src/components/PromGraphCpt/index.tsx +++ b/src/components/PromGraphCpt/index.tsx @@ -64,6 +64,7 @@ interface IProps { promQLInputTooltip?: string; extra?: React.ReactElement; showExportButton?: boolean; // 是否显示导出按钮 + refetchOnZoom?: boolean; } const TabPane = Tabs.TabPane; @@ -98,6 +99,7 @@ export default function index(props: IProps) { extra, defaultRange, showExportButton, + refetchOnZoom = false, } = props; const [value, setValue] = useState(promQL); // for promQLInput const [queryStats, setQueryStats] = useState(null); @@ -292,6 +294,7 @@ export default function index(props: IProps) { graphStandardOptionsType={graphStandardOptionsType} graphStandardOptionsPlacement={graphStandardOptionsPlacement} defaultUnit={defaultUnit} + refetchOnZoom={refetchOnZoom} /> diff --git a/src/components/PromGraphCpt/style.less b/src/components/PromGraphCpt/style.less index c2b53892e..45c6fb366 100644 --- a/src/components/PromGraphCpt/style.less +++ b/src/components/PromGraphCpt/style.less @@ -118,6 +118,12 @@ display: flex; flex-direction: column; + &.prom-graph-graph-zoom-owner { + .ts-graph-zoom-resetBtn { + display: block !important; + } + } + .renderer-timeseries-container { display: flex !important; flex-direction: column; @@ -148,4 +154,4 @@ &:hover { background-color: var(--fc-fill-3); } -} \ No newline at end of file +} diff --git a/src/locales/common/locale/zh_CN.ts b/src/locales/common/locale/zh_CN.ts index 1282f3271..6053e2cce 100644 --- a/src/locales/common/locale/zh_CN.ts +++ b/src/locales/common/locale/zh_CN.ts @@ -4,7 +4,7 @@ const zh_CN = { '403_admin': '管理员:', '403_back': '返回上一页', 404: '你访问的页面不存在!', - '404_btn': '回到首页', + '404_btn': '返回首页', }, business_group: '业务组', business_groups: '业务组', diff --git a/src/locales/common/locale/zh_HK.ts b/src/locales/common/locale/zh_HK.ts index fec41a3be..bfa88fd33 100644 --- a/src/locales/common/locale/zh_HK.ts +++ b/src/locales/common/locale/zh_HK.ts @@ -4,7 +4,7 @@ const zh_HK = { '403_admin': '管理員:', '403_back': '返回上一頁', 404: '您訪問的頁面不存在!', - '404_btn': '回到首頁', + '404_btn': '返回首頁', }, business_group: '業務組', business_groups: '業務組', diff --git a/src/main.tsx b/src/main.tsx index e58aff816..44632266a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -20,10 +20,14 @@ import { i18nInit } from './i18n'; // loaded and initialized first import App from './App'; import { I18nextProvider } from 'react-i18next'; import { initTheme } from './utils/darkMode'; +import { initFlashcatFrom } from './utils/flashcatFrom'; // 在页面渲染前初始化主题,避免样式闪烁 initTheme(); +// 指向官网/Flashduty 的链接统一携带 from=n9e-user 渠道参数 +initFlashcatFrom(); + ReactDOM.render( diff --git a/src/pages/dashboard/Editor/Fields/Legend/index.tsx b/src/pages/dashboard/Editor/Fields/Legend/index.tsx index 71e9bedc1..57ce8ca94 100644 --- a/src/pages/dashboard/Editor/Fields/Legend/index.tsx +++ b/src/pages/dashboard/Editor/Fields/Legend/index.tsx @@ -71,6 +71,35 @@ export default function index() { + + + + +