From 74cb089271faad911d1e724ff655b8b75a73bfe8 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Thu, 26 May 2022 13:04:43 +0800 Subject: [PATCH 01/15] feat: sort spans with `startTime` or `spanId` in a segment (#100) --- src/store/modules/ebpf.ts | 3 ++ src/types/trace.d.ts | 9 ++++- src/views/dashboard/graphs/TopList.vue | 14 ++++++-- .../graphs/components/ColumnGraph.vue | 2 +- src/views/dashboard/related/ebpf/Content.vue | 2 +- .../related/ebpf/components/EBPFSchedules.vue | 7 ---- .../related/ebpf/components/EBPFStack.vue | 12 ++++--- .../dashboard/related/profile/Content.vue | 2 +- src/views/dashboard/related/trace/Detail.vue | 13 ++----- .../trace/components/D3Graph/Index.vue | 36 +++++++++++++++---- .../trace/components/D3Graph/SpanDetail.vue | 2 +- 11 files changed, 68 insertions(+), 34 deletions(-) diff --git a/src/store/modules/ebpf.ts b/src/store/modules/ebpf.ts index f43c649f..987422b8 100644 --- a/src/store/modules/ebpf.ts +++ b/src/store/modules/ebpf.ts @@ -35,6 +35,7 @@ interface EbpfStore { couldProfiling: boolean; tip: string; selectedTask: Recordable; + aggregateType: string; } export const ebpfStore = defineStore({ @@ -48,6 +49,7 @@ export const ebpfStore = defineStore({ couldProfiling: false, tip: "", selectedTask: {}, + aggregateType: "COUNT", }), actions: { setSelectedTask(task: EBPFTaskList) { @@ -131,6 +133,7 @@ export const ebpfStore = defineStore({ timeRanges: Array<{ start: number; end: number }>; aggregateType: string; }) { + this.aggregateType = params.aggregateType; if (!params.scheduleIdList.length) { return new Promise((resolve) => resolve({})); } diff --git a/src/types/trace.d.ts b/src/types/trace.d.ts index 82e0f669..569e7173 100644 --- a/src/types/trace.d.ts +++ b/src/types/trace.d.ts @@ -46,8 +46,15 @@ export interface Span { children?: Span[]; tags?: Array>; logs?: log[]; + parentSegmentId?: string; + refs?: Ref[]; } - +export type Ref = { + type: string; + parentSegmentId: string; + parentSpanId: number; + traceId: string; +}; export interface log { time: number; data: Map; diff --git a/src/views/dashboard/graphs/TopList.vue b/src/views/dashboard/graphs/TopList.vue index 8fe0f72f..dcfcab29 100644 --- a/src/views/dashboard/graphs/TopList.vue +++ b/src/views/dashboard/graphs/TopList.vue @@ -17,13 +17,13 @@ limitations under the License. -->
-
+
{{ i.value }} {{ i.name }}
-
+
diff --git a/src/views/dashboard/related/ebpf/Content.vue b/src/views/dashboard/related/ebpf/Content.vue index 019a20c4..f308004e 100644 --- a/src/views/dashboard/related/ebpf/Content.vue +++ b/src/views/dashboard/related/ebpf/Content.vue @@ -38,7 +38,7 @@ import EBPFStack from "./components/EBPFStack.vue"; .vis-graph { height: 100%; - width: calc(100% - 300px); + flex-grow: 2; min-width: 700px; overflow: auto; } diff --git a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue index 191b0097..7609aba8 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue @@ -252,13 +252,6 @@ watch( min-width: 560px; } -.schedules { - width: calc(100% - 5px); - margin: 0 5px 5px 0; - height: calc(100% - 60px); - min-height: 150px; -} - .inputs { width: 350px; } diff --git a/src/views/dashboard/related/ebpf/components/EBPFStack.vue b/src/views/dashboard/related/ebpf/components/EBPFStack.vue index adccea90..b69d62f6 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFStack.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFStack.vue @@ -24,6 +24,7 @@ import d3tip from "d3-tip"; import { flamegraph } from "d3-flame-graph"; import { useEbpfStore } from "@/store/modules/ebpf"; import { StackElement } from "@/types/ebpf"; +import { AggregateTypes } from "./data"; import "d3-flame-graph/dist/d3-flamegraph.css"; /*global Nullable*/ @@ -86,10 +87,13 @@ function drawGraph() { const tip = (d3tip as any)() .attr("class", "d3-tip") .direction("w") - .html( - (d: { data: StackElement }) => - `
Symbol: ${d.data.name}
Dump Count: ${d.data.dumpCount}
` - ) + .html((d: { data: StackElement }) => { + const valStr = + ebpfStore.aggregateType === AggregateTypes[0].value + ? `
Dump Count: ${d.data.dumpCount}
` + : `
Duration: ${d.data.dumpCount} ns
`; + return `
Symbol: ${d.data.name}
${valStr}`; + }) .style("max-width", "500px"); flameChart.value.tooltip(tip); d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value); diff --git a/src/views/dashboard/related/profile/Content.vue b/src/views/dashboard/related/profile/Content.vue index de5cc6ce..c5260ba7 100644 --- a/src/views/dashboard/related/profile/Content.vue +++ b/src/views/dashboard/related/profile/Content.vue @@ -56,7 +56,7 @@ function loadTrees(l: boolean) { .item { height: 100%; - width: calc(100% - 300px); + flex-grow: 2; overflow: auto; } diff --git a/src/views/dashboard/related/trace/Detail.vue b/src/views/dashboard/related/trace/Detail.vue index 4b2cda82..4d217ce1 100644 --- a/src/views/dashboard/related/trace/Detail.vue +++ b/src/views/dashboard/related/trace/Detail.vue @@ -153,7 +153,6 @@ import { useI18n } from "vue-i18n"; import { useTraceStore } from "@/store/modules/trace"; import { Option } from "@/types/app"; import copy from "@/utils/copy"; -import List from "./components/List.vue"; import graphs from "./components/index"; import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue"; import { ElMessage } from "element-plus"; @@ -162,7 +161,6 @@ export default defineComponent({ name: "TraceDetail", components: { ...graphs, - List, LogTable, }, setup() { @@ -182,14 +180,8 @@ export default defineComponent({ dayjs(date).format(pattern); const showTraceLogs = ref(false); - function handleClick(ids: string[] | any) { - let copyValue = null; - if (ids.length === 1) { - copyValue = ids[0]; - } else { - copyValue = ids.join(","); - } - copy(copyValue); + function handleClick() { + copy(traceId.value || traceStore.currentTrace.traceIds[0].value); } async function changeTraceId(opt: Option[] | any) { @@ -235,6 +227,7 @@ export default defineComponent({ pageNum, loading, total, + traceId, }; }, }); diff --git a/src/views/dashboard/related/trace/components/D3Graph/Index.vue b/src/views/dashboard/related/trace/components/D3Graph/Index.vue index 893a45fb..35f3db68 100644 --- a/src/views/dashboard/related/trace/components/D3Graph/Index.vue +++ b/src/views/dashboard/related/trace/components/D3Graph/Index.vue @@ -31,7 +31,7 @@ import _ from "lodash"; import * as d3 from "d3"; import ListGraph from "../../utils/d3-trace-list"; import TreeGraph from "../../utils/d3-trace-tree"; -import { Span } from "@/types/trace"; +import { Span, Ref } from "@/types/trace"; import SpanDetail from "./SpanDetail.vue"; /* global defineProps, Nullable, defineExpose*/ @@ -45,6 +45,7 @@ const showDetail = ref(false); const fixSpansSize = ref(0); const segmentId = ref([]); const currentSpan = ref>([]); +const refSpans = ref>([]); const tree = ref(null); const traceGraph = ref>(null); defineExpose({ @@ -103,14 +104,17 @@ function changeTree() { segmentId.value = []; const segmentGroup: any = {}; const segmentIdGroup: any = []; - const fixSpans: any[] = []; - const segmentHeaders: any = []; + const fixSpans: Span[] = []; + const segmentHeaders: Span[] = []; for (const span of props.data) { + if (span.refs.length) { + refSpans.value.push(...span.refs); + } if (span.parentSpanId === -1) { segmentHeaders.push(span); } else { - const index = props.data.findIndex( - (i: any) => + const item = props.data.find( + (i: Span) => i.segmentId === span.segmentId && i.spanId === span.spanId - 1 ); const fixSpanKeyContent = { @@ -119,7 +123,7 @@ function changeTree() { spanId: span.spanId - 1, parentSpanId: span.spanId - 2, }; - if (index === -1 && !_.find(fixSpans, fixSpanKeyContent)) { + if (!item && !_.find(fixSpans, fixSpanKeyContent)) { fixSpans.push({ ...fixSpanKeyContent, refs: [], @@ -133,6 +137,8 @@ function changeTree() { layer: "Broken", tags: [], logs: [], + startTime: 0, + endTime: 0, }); } } @@ -167,6 +173,8 @@ function changeTree() { layer: "Broken", tags: [], logs: [], + startTime: 0, + endTime: 0, }); } // if root broken node is not exist, create a root broken node. @@ -191,6 +199,8 @@ function changeTree() { layer: "Broken", tags: [], logs: [], + startTime: 0, + endTime: 0, }); } } @@ -268,14 +278,28 @@ function changeTree() { } function collapse(d: Span) { if (d.children) { + const item = refSpans.value.find( + (s: Ref) => + s.parentSpanId === d.spanId && s.parentSegmentId === d.segmentId + ); let dur = d.endTime - d.startTime; d.children.forEach((i: Span) => { dur -= i.endTime - i.startTime; }); d.dur = dur < 0 ? 0 : dur; + if (item) { + d.children = d.children.sort(compare("startTime")); + } d.children.forEach((i: Span) => collapse(i)); } } +function compare(p: string) { + return (m: any, n: any) => { + const a = m[p]; + const b = n[p]; + return a - b; + }; +} onBeforeUnmount(() => { d3.selectAll(".d3-tip").remove(); window.removeEventListener("resize", resize); diff --git a/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue b/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue index 89a1046d..6aee1770 100644 --- a/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue +++ b/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue @@ -52,7 +52,7 @@ limitations under the License. --> class="grey link-hover cp ml-5" @click="copy(i.value)" > - +
From 4d26728eb5bf409ddaa21dde3967fe3b24ce29e6 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Mon, 30 May 2022 18:18:37 +0800 Subject: [PATCH 02/15] feat: add `Percentage Of Root` and `Percentage Of Selected` in the eBPF widget (#101) --- src/types/ebpf.d.ts | 2 ++ .../related/ebpf/components/EBPFSchedules.vue | 2 +- .../related/ebpf/components/EBPFStack.vue | 26 +++++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/types/ebpf.d.ts b/src/types/ebpf.d.ts index 4598528e..40852074 100644 --- a/src/types/ebpf.d.ts +++ b/src/types/ebpf.d.ts @@ -65,6 +65,8 @@ export type StackElement = { stackType: string; value: number; children?: StackElement[]; + rateOfRoot?: string; + rateOfParent: string; }; export type AnalyzationTrees = { id: string; diff --git a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue index 7609aba8..d58dfe91 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue @@ -173,7 +173,7 @@ async function analyzeEBPF() { timeRanges, aggregateType: aggregateType.value, }); - if (res.data.errors) { + if (res.data && res.data.errors) { ElMessage.error(res.data.errors); return; } diff --git a/src/views/dashboard/related/ebpf/components/EBPFStack.vue b/src/views/dashboard/related/ebpf/components/EBPFStack.vue index b69d62f6..9d8b6ab9 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFStack.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFStack.vue @@ -30,6 +30,7 @@ import "d3-flame-graph/dist/d3-flamegraph.css"; /*global Nullable*/ const ebpfStore = useEbpfStore(); const stackTree = ref>(null); +const selectStack = ref>(null); const graph = ref>(null); const flameChart = ref(null); const min = ref(1); @@ -52,6 +53,8 @@ function drawGraph() { symbol: "Virtual Root", dumpCount: 0, stackType: "", + rateOfRoot: "", + rateOfParent: "", }; countRange(); for (const tree of ebpfStore.analyzeTrees) { @@ -81,18 +84,36 @@ function drawGraph() { .title("") .selfValue(false) .inverted(true) + .onClick((d: { data: StackElement }) => { + selectStack.value = d.data; + }) .setColorMapper((d, originalColor) => d.highlight ? "#6aff8f" : originalColor ); const tip = (d3tip as any)() .attr("class", "d3-tip") .direction("w") - .html((d: { data: StackElement }) => { + .html((d: { data: StackElement } & { parent: { data: StackElement } }) => { + const name = d.data.name.replace("<", "<").replace(">", ">"); const valStr = ebpfStore.aggregateType === AggregateTypes[0].value ? `
Dump Count: ${d.data.dumpCount}
` : `
Duration: ${d.data.dumpCount} ns
`; - return `
Symbol: ${d.data.name}
${valStr}`; + const rateOfParent = + (d.parent && + `
Percentage Of Selected: ${ + ( + (d.data.dumpCount / + ((selectStack.value && selectStack.value.dumpCount) || + root.dumpCount)) * + 100 + ).toFixed(3) + "%" + }
`) || + ""; + const rateOfRoot = `
Percentage Of Root: ${ + ((d.data.dumpCount / root.dumpCount) * 100).toFixed(3) + "%" + }
`; + return `
Symbol: ${name}
${valStr}${rateOfParent}${rateOfRoot}`; }) .style("max-width", "500px"); flameChart.value.tooltip(tip); @@ -122,6 +143,7 @@ function processTree(arr: StackElement[]) { obj[item.originId] = item; } const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]); + for (const item of copyArr) { if (item.parentId === "1") { const val = Number(scale(item.dumpCount).toFixed(4)); From 0a746a125be69bfad68c43d4d8d8502cfec1f3b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jun 2022 10:00:40 +0800 Subject: [PATCH 03/15] build(deps): bump eventsource from 1.1.0 to 1.1.1 (#102) --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a53afd05..fe87d6cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11080,9 +11080,9 @@ } }, "node_modules/eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz", + "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==", "dev": true, "dependencies": { "original": "^1.0.0" @@ -37656,9 +37656,9 @@ "dev": true }, "eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz", + "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==", "dev": true, "requires": { "original": "^1.0.0" From 30069ce82505c973b624531209611cea32d6db10 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Thu, 2 Jun 2022 20:09:38 +0800 Subject: [PATCH 04/15] feat: visualize a on-demand log widget (#99) --- package-lock.json | 61 +++ package.json | 2 + src/assets/icons/demand.svg | 16 + src/graphql/fragments/demand-log.ts | 38 ++ src/graphql/index.ts | 2 + src/graphql/query/demand-log.ts | 22 + src/hooks/useEcharts.ts | 4 + src/locales/lang/en.ts | 6 + src/locales/lang/es.ts | 19 +- src/locales/lang/zh.ts | 6 + src/store/modules/dashboard.ts | 11 +- src/store/modules/demand-log.ts | 124 ++++++ src/types/app.d.ts | 4 + src/types/demand-log.ts | 31 ++ src/types/monaco-editor.ts | 17 + src/views/dashboard/controls/DemandLog.vue | 93 ++++ src/views/dashboard/controls/Tab.vue | 11 +- src/views/dashboard/controls/index.ts | 13 +- src/views/dashboard/data.ts | 2 + src/views/dashboard/panel/Tool.vue | 6 + .../dashboard/related/demand-log/Content.vue | 78 ++++ .../dashboard/related/demand-log/Header.vue | 412 ++++++++++++++++++ .../dashboard/related/demand-log/data.ts | 37 ++ .../related/ebpf/components/EBPFSchedules.vue | 2 +- vue.config.js | 9 +- 25 files changed, 1012 insertions(+), 14 deletions(-) create mode 100644 src/assets/icons/demand.svg create mode 100644 src/graphql/fragments/demand-log.ts create mode 100644 src/graphql/query/demand-log.ts create mode 100644 src/store/modules/demand-log.ts create mode 100644 src/types/demand-log.ts create mode 100644 src/types/monaco-editor.ts create mode 100644 src/views/dashboard/controls/DemandLog.vue create mode 100644 src/views/dashboard/related/demand-log/Content.vue create mode 100644 src/views/dashboard/related/demand-log/Header.vue create mode 100644 src/views/dashboard/related/demand-log/data.ts diff --git a/package-lock.json b/package-lock.json index fe87d6cc..c908f59a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "echarts": "^5.2.2", "element-plus": "^2.0.2", "lodash": "^4.17.21", + "monaco-editor": "^0.27.0", "pinia": "^2.0.5", "vue": "^3.0.0", "vue-grid-layout": "^3.0.0-beta1", @@ -50,6 +51,7 @@ "eslint-plugin-vue": "^7.0.0", "husky": "^7.0.4", "lint-staged": "^12.1.3", + "monaco-editor-webpack-plugin": "^4.1.2", "node-sass": "^6.0.1", "postcss-html": "^1.3.0", "postcss-scss": "^4.0.2", @@ -18234,6 +18236,38 @@ "node": "*" } }, + "node_modules/monaco-editor": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz", + "integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ==" + }, + "node_modules/monaco-editor-webpack-plugin": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.1.2.tgz", + "integrity": "sha512-snmHecygICKT0UlHhva+Cs2WaLPpxy3111xbvInhjjTr5m0xQTFHlmJ2QQDcB14Vzmm7f07uc1TtbvOpmL50BA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0" + }, + "peerDependencies": { + "monaco-editor": "0.25.x || 0.26.x || 0.27.x", + "webpack": "^4.5.0 || 5.x" + } + }, + "node_modules/monaco-editor-webpack-plugin/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -43271,6 +43305,33 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", "dev": true }, + "monaco-editor": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz", + "integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ==" + }, + "monaco-editor-webpack-plugin": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.1.2.tgz", + "integrity": "sha512-snmHecygICKT0UlHhva+Cs2WaLPpxy3111xbvInhjjTr5m0xQTFHlmJ2QQDcB14Vzmm7f07uc1TtbvOpmL50BA==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/package.json b/package.json index ece16b3a..e1f48cc3 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "echarts": "^5.2.2", "element-plus": "^2.0.2", "lodash": "^4.17.21", + "monaco-editor": "^0.27.0", "pinia": "^2.0.5", "vue": "^3.0.0", "vue-grid-layout": "^3.0.0-beta1", @@ -52,6 +53,7 @@ "eslint-plugin-vue": "^7.0.0", "husky": "^7.0.4", "lint-staged": "^12.1.3", + "monaco-editor-webpack-plugin": "^4.1.2", "node-sass": "^6.0.1", "postcss-html": "^1.3.0", "postcss-scss": "^4.0.2", diff --git a/src/assets/icons/demand.svg b/src/assets/icons/demand.svg new file mode 100644 index 00000000..4b1b60db --- /dev/null +++ b/src/assets/icons/demand.svg @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/src/graphql/fragments/demand-log.ts b/src/graphql/fragments/demand-log.ts new file mode 100644 index 00000000..40df4977 --- /dev/null +++ b/src/graphql/fragments/demand-log.ts @@ -0,0 +1,38 @@ +/** + * 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. + */ + +export const queryContainers = { + variable: "$condition: OndemandContainergQueryCondition!", + query: ` + containers: listContainers(condition: $condition) { + errorReason + containers + }`, +}; + +export const queryStreamingLogs = { + variable: "$condition: OndemandLogQueryCondition", + query: ` + logs: ondemandPodLogs(condition: $condition) { + errorReason + logs { + timestamp + contentType + content + } + }`, +}; diff --git a/src/graphql/index.ts b/src/graphql/index.ts index 3b76c9fb..19271544 100644 --- a/src/graphql/index.ts +++ b/src/graphql/index.ts @@ -26,6 +26,7 @@ import * as profile from "./query/profile"; import * as alarm from "./query/alarm"; import * as event from "./query/event"; import * as ebpf from "./query/ebpf"; +import * as demandLog from "./query/demand-log"; const query: { [key: string]: string } = { ...app, @@ -38,6 +39,7 @@ const query: { [key: string]: string } = { ...alarm, ...event, ...ebpf, + ...demandLog, }; class Graphql { private queryData = ""; diff --git a/src/graphql/query/demand-log.ts b/src/graphql/query/demand-log.ts new file mode 100644 index 00000000..94fd1432 --- /dev/null +++ b/src/graphql/query/demand-log.ts @@ -0,0 +1,22 @@ +/** + * 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 { queryContainers, queryStreamingLogs } from "../fragments/demand-log"; + +export const fetchContainers = `query listContainers(${queryContainers.variable}) {${queryContainers.query}}`; + +export const fetchDemandPodLogs = `query ondemandPodLogs(${queryStreamingLogs.variable}) {${queryStreamingLogs.query}}`; diff --git a/src/hooks/useEcharts.ts b/src/hooks/useEcharts.ts index 16b505b2..01f7d7ed 100644 --- a/src/hooks/useEcharts.ts +++ b/src/hooks/useEcharts.ts @@ -79,7 +79,11 @@ export function useECharts( if (!el || !unref(el)) { return; } + const { width, height } = el.getBoundingClientRect(); + if (!width || !height) { + return; + } chartInstance = echarts.init(el, t); const { removeEvent } = useEventListener({ el: window, diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts index 7f55d4a0..7749d634 100644 --- a/src/locales/lang/en.ts +++ b/src/locales/lang/en.ts @@ -136,7 +136,13 @@ const msg = { targetType: "Target Type", ebpfTip: "Don't have a process for profiling", processSelect: "Click to select processes", + container: "Container", + limit: "Limit", page: "Page", + interval: "Refresh Interval", + pause: "Pause", + begin: "Start", + seconds: "Seconds", hourTip: "Select Hour", minuteTip: "Select Minute", secondTip: "Select Second", diff --git a/src/locales/lang/es.ts b/src/locales/lang/es.ts index 40ef1fda..c5c8a58e 100644 --- a/src/locales/lang/es.ts +++ b/src/locales/lang/es.ts @@ -74,8 +74,10 @@ const msg = { editGraph: "Editar Opciones", dashboardName: "Selecciona Nombre del Panel", linkDashboard: "Nombre del panel relacionado con llamadas de la topología", - linkServerMetrics: "Métricas de servidor relacionadas con llamadas de la topología", - linkClientMetrics: "Métricas de cliente relacionadas con llamadas de la topología", + linkServerMetrics: + "Métricas de servidor relacionadas con llamadas de la topología", + linkClientMetrics: + "Métricas de cliente relacionadas con llamadas de la topología", nodeDashboard: "Nombre del panel relacionado con nodos de la topología", nodeMetrics: "Mêtricas relacionas con nodos de la topología", instanceDashboard: "Nombre del panel relacionado con instancias de servicio", @@ -137,6 +139,10 @@ const msg = { ebpfTip: "Le falta el proceso para perfilar", processSelect: "Click para seleccionar proceso", page: "Página", + interval: "Intervalo de actualización", + pause: "Pausa", + begin: "Inicio", + seconds: "Segundos", hourTip: "Seleccione Hora", minuteTip: "Seleccione Minuto", secondTip: "Seleccione Segundo", @@ -303,7 +309,8 @@ const msg = { destEndpoint: "Endpoint Destinación", eventSource: "Fuente Envento", modalTitle: "Inspección", - selectRedirectPage: "Quiere inspeccionar las Trazas or Registros de datos del servicio %s?", + selectRedirectPage: + "Quiere inspeccionar las Trazas or Registros de datos del servicio %s?", logAnalysis: "Lenguaje de Análisis de Registro de Datos", logDataBody: "Contenido del Registro de Datos", addType: "Por favor introduzca un tipo", @@ -324,8 +331,10 @@ const msg = { addTraceID: "Por favor introduzca el ID de la traza", addTags: "Por favor introduzaca una etiqueta", addKeywordsOfContent: "Por favor introduzca una clave de contenido", - addExcludingKeywordsOfContent: "Por favor introduzca una clave excluyente de contenido", - noticeTag: "Por favor presione Intro después de introducir una etiqueta(clave=valor).", + addExcludingKeywordsOfContent: + "Por favor introduzca una clave excluyente de contenido", + noticeTag: + "Por favor presione Intro después de introducir una etiqueta(clave=valor).", conditionNotice: "Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).", language: "Lenguaje", diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts index 88af9d13..10d8837a 100644 --- a/src/locales/lang/zh.ts +++ b/src/locales/lang/zh.ts @@ -134,7 +134,13 @@ const msg = { targetType: "目标类型", processSelect: "点击选择进程", ebpfTip: "没有进程可以分析", + container: "容器", + limit: "范围", page: "页面", + interval: "刷新间隔时间", + pause: "暂停", + begin: "开始", + seconds: "秒", hourTip: "选择小时", minuteTip: "选择分钟", secondTip: "选择秒数", diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts index 2ed20f17..596ac8b6 100644 --- a/src/store/modules/dashboard.ts +++ b/src/store/modules/dashboard.ts @@ -114,7 +114,8 @@ export const dashboardStore = defineStore({ type === "Trace" || type === "Profile" || type === "Log" || - type === "Ebpf" + type === "Ebpf" || + type === "DemandLog" ) { newItem.h = 36; } @@ -170,7 +171,13 @@ export const dashboardStore = defineStore({ showDepth: true, }; } - if (type === "Trace" || type === "Profile" || type === "Log") { + if ( + type === "Trace" || + type === "Profile" || + type === "Log" || + type === "DemandLog" || + type === "Ebpf" + ) { newItem.h = 32; } if (type === "Text") { diff --git a/src/store/modules/demand-log.ts b/src/store/modules/demand-log.ts new file mode 100644 index 00000000..0eaf7d1e --- /dev/null +++ b/src/store/modules/demand-log.ts @@ -0,0 +1,124 @@ +import { ElMessage } from "element-plus"; +/** + * 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 { defineStore } from "pinia"; +import { Instance } from "@/types/selector"; +import { store } from "@/store"; +import graphql from "@/graphql"; +import { AxiosResponse } from "axios"; +import { useAppStoreWithOut } from "@/store/modules/app"; +import { useSelectorStore } from "@/store/modules/selectors"; +import { Conditions, Log } from "@/types/demand-log"; + +interface DemandLogState { + containers: Instance[]; + instances: Instance[]; + conditions: Conditions; + selectorStore: any; + logs: Log[]; + loadLogs: boolean; + message: string; +} + +export const demandLogStore = defineStore({ + id: "demandLog", + state: (): DemandLogState => ({ + containers: [{ label: "", value: "" }], + instances: [{ value: "", label: "" }], + conditions: { + container: "", + serviceInstanceId: "", + duration: useAppStoreWithOut().durationTime, + }, + selectorStore: useSelectorStore(), + logs: [], + loadLogs: false, + message: "", + }), + actions: { + setLogCondition(data: Conditions) { + this.conditions = { ...this.conditions, ...data }; + }, + setLogs(logs: Log[], message?: string) { + this.logs = logs; + this.message = message || ""; + }, + async getInstances(id: string) { + const serviceId = this.selectorStore.currentService + ? this.selectorStore.currentService.id + : id; + const res: AxiosResponse = await graphql.query("queryInstances").params({ + serviceId, + duration: useAppStoreWithOut().durationTime, + }); + + if (res.data.errors) { + return res.data; + } + this.instances = res.data.data.pods || []; + return res.data; + }, + async getContainers(serviceInstanceId: string) { + if (!serviceInstanceId) { + return new Promise((resolve) => + resolve({ errors: "No service instance" }) + ); + } + const condition = { + serviceInstanceId, + }; + const res: AxiosResponse = await graphql + .query("fetchContainers") + .params({ condition }); + + if (res.data.errors) { + return res.data; + } + if (res.data.data.containers.errorReason) { + this.containers = [{ label: "", value: "" }]; + return res.data; + } + this.containers = res.data.data.containers.containers.map((d: string) => { + return { label: d, value: d }; + }); + return res.data; + }, + async getDemandLogs() { + this.loadLogs = true; + const res: AxiosResponse = await graphql + .query("fetchDemandPodLogs") + .params({ condition: this.conditions }); + this.loadLogs = false; + if (res.data.errors) { + return res.data; + } + if (res.data.data.logs.errorReason) { + this.setLogs("", res.data.data.logs.errorReason); + return res.data; + } + const logs = res.data.data.logs.logs + .map((d: Log) => d.content) + .join("\n"); + this.setLogs(logs); + return res.data; + }, + }, +}); + +export function useDemandLogStore(): any { + return demandLogStore(store); +} diff --git a/src/types/app.d.ts b/src/types/app.d.ts index 74e69c32..ee35299a 100644 --- a/src/types/app.d.ts +++ b/src/types/app.d.ts @@ -28,3 +28,7 @@ export interface DurationTime { end: string; step: string; } +export type Paging = { + pageNum: number; + pageSize: number; +}; diff --git a/src/types/demand-log.ts b/src/types/demand-log.ts new file mode 100644 index 00000000..44a24ab7 --- /dev/null +++ b/src/types/demand-log.ts @@ -0,0 +1,31 @@ +/** + * 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 { DurationTime } from "./app"; + +export interface Conditions { + container: string; + serviceInstanceId: string; + duration: DurationTime; + keywordsOfContent?: string[]; + excludingKeywordsOfContent?: string; +} + +export interface Log { + content: string; + timestamp: number; + contentType: string; +} diff --git a/src/types/monaco-editor.ts b/src/types/monaco-editor.ts new file mode 100644 index 00000000..0e7645a5 --- /dev/null +++ b/src/types/monaco-editor.ts @@ -0,0 +1,17 @@ +/** + * 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. + */ +declare module "monaco-editor"; diff --git a/src/views/dashboard/controls/DemandLog.vue b/src/views/dashboard/controls/DemandLog.vue new file mode 100644 index 00000000..543bad00 --- /dev/null +++ b/src/views/dashboard/controls/DemandLog.vue @@ -0,0 +1,93 @@ + + + + diff --git a/src/views/dashboard/controls/Tab.vue b/src/views/dashboard/controls/Tab.vue index 524e9f7e..50642870 100644 --- a/src/views/dashboard/controls/Tab.vue +++ b/src/views/dashboard/controls/Tab.vue @@ -110,6 +110,7 @@ import Log from "./Log.vue"; import Text from "./Text.vue"; import Ebpf from "./Ebpf.vue"; import { dragIgnoreFrom } from "../data"; +import DemandLog from "./DemandLog.vue"; const props = { data: { @@ -120,7 +121,7 @@ const props = { }; export default defineComponent({ name: "Tab", - components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf }, + components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf, DemandLog }, props, setup(props) { const { t } = useI18n(); @@ -250,21 +251,23 @@ export default defineComponent({ span { display: inline-block; - padding: 0 10px; - margin: 0 10px; height: 40px; line-height: 40px; cursor: pointer; + text-align: center; } .tab-name { - max-width: 130px; + max-width: 110px; height: 20px; line-height: 20px; outline: none; color: #333; font-style: normal; margin-right: 5px; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; } .tab-icons { diff --git a/src/views/dashboard/controls/index.ts b/src/views/dashboard/controls/index.ts index 01fc5dc5..858bc5c5 100644 --- a/src/views/dashboard/controls/index.ts +++ b/src/views/dashboard/controls/index.ts @@ -22,5 +22,16 @@ import Profile from "./Profile.vue"; import Log from "./Log.vue"; import Text from "./Text.vue"; import Ebpf from "./Ebpf.vue"; +import DemandLog from "./DemandLog.vue"; -export default { Tab, Widget, Trace, Topology, Profile, Log, Text, Ebpf }; +export default { + Tab, + Widget, + Trace, + Topology, + Profile, + Log, + Text, + Ebpf, + DemandLog, +}; diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts index c6e4bc32..b19f65e7 100644 --- a/src/views/dashboard/data.ts +++ b/src/views/dashboard/data.ts @@ -186,6 +186,7 @@ export const ServiceTools = [ { name: "timeline", content: "Trace Profiling", id: "addProfile" }, { name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" }, { name: "assignment", content: "Log", id: "addLog" }, + { name: "demand", content: "Add On Demand Log", id: "addDemandLog" }, ]; export const InstanceTools = [ { name: "playlist_add", content: "Add Widget", id: "addWidget" }, @@ -193,6 +194,7 @@ export const InstanceTools = [ { name: "library_books", content: "Add Text", id: "addText" }, { name: "merge", content: "Add Trace", id: "addTrace" }, { name: "assignment", content: "Add Log", id: "addLog" }, + { name: "demand", content: "Add On Demand Log", id: "addDemandLog" }, ]; export const EndpointTools = [ { name: "playlist_add", content: "Add Widget", id: "addWidget" }, diff --git a/src/views/dashboard/panel/Tool.vue b/src/views/dashboard/panel/Tool.vue index bc96d8c3..8d8ed374 100644 --- a/src/views/dashboard/panel/Tool.vue +++ b/src/views/dashboard/panel/Tool.vue @@ -455,6 +455,9 @@ function setTabControls(id: string) { case "addText": dashboardStore.addTabControls("Text"); break; + case "addDemandLog": + dashboardStore.addTabControls("DemandLog"); + break; default: ElMessage.info("Don't support this control"); break; @@ -487,6 +490,9 @@ function setControls(id: string) { case "addText": dashboardStore.addControl("Text"); break; + case "addDemandLog": + dashboardStore.addControl("DemandLog"); + break; default: dashboardStore.addControl("Widget"); } diff --git a/src/views/dashboard/related/demand-log/Content.vue b/src/views/dashboard/related/demand-log/Content.vue new file mode 100644 index 00000000..a3577f97 --- /dev/null +++ b/src/views/dashboard/related/demand-log/Content.vue @@ -0,0 +1,78 @@ + + + + diff --git a/src/views/dashboard/related/demand-log/Header.vue b/src/views/dashboard/related/demand-log/Header.vue new file mode 100644 index 00000000..3164bc9c --- /dev/null +++ b/src/views/dashboard/related/demand-log/Header.vue @@ -0,0 +1,412 @@ + + + + diff --git a/src/views/dashboard/related/demand-log/data.ts b/src/views/dashboard/related/demand-log/data.ts new file mode 100644 index 00000000..0ef8c235 --- /dev/null +++ b/src/views/dashboard/related/demand-log/data.ts @@ -0,0 +1,37 @@ +/** + * 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. + */ + +export const TimeRanges = [ + { label: "From 5 seconds ago -- Now", value: 5 }, + { label: "From 10 seconds ago -- Now", value: 10 }, + { label: "From 15 seconds ago -- Now", value: 15 }, + { label: "From 30 seconds ago -- Now", value: 30 }, + { label: "From 45 seconds ago -- Now", value: 45 }, + { label: "From 1 minute ago -- Now", value: 60 }, + { label: "From 5 minutes ago -- Now", value: 300 }, + { label: "From 15 minutes ago -- Now", value: 900 }, + { label: "From 30 minutes ago -- Now", value: 1800 }, +]; +export const IntervalOpts = [ + { label: "None", value: 0 }, + { label: "5 seconds", value: 5 }, + { label: "10 seconds", value: 10 }, + { label: "15 seconds", value: 15 }, + { label: "30 seconds", value: 30 }, + { label: "45 seconds", value: 45 }, + { label: "1 minute", value: 60 }, +]; diff --git a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue index d58dfe91..21539fd1 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue @@ -253,7 +253,7 @@ watch( } .inputs { - width: 350px; + width: 400px; } .input-with-search { diff --git a/vue.config.js b/vue.config.js index dd9d016c..d26454e6 100644 --- a/vue.config.js +++ b/vue.config.js @@ -15,6 +15,7 @@ * limitations under the License. */ +const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); const AutoImport = require("unplugin-auto-import/webpack"); const Components = require("unplugin-vue-components/webpack"); const { ElementPlusResolver } = require("unplugin-vue-components/resolvers"); @@ -64,6 +65,11 @@ module.exports = { test: /[\\/]node_modules[\\/]echarts|zrender[\\/]/, priority: 30, }, + monacoEditor: { + name: "monaco-editor", + test: /[\\/]node_modules[\\/]monaco-editor[\\/]/, + priority: 40, + }, elementPlus: { name: "element-plus", test: /[\\/]node_modules[\\/]element-plus|@element-plus[\\/]/, @@ -98,7 +104,8 @@ module.exports = { Components({ resolvers: [ElementPlusResolver({ importStyle: "css" })], dts: "./src/types/components.d.ts", - }) + }), + new MonacoWebpackPlugin() ); }, }; From c875c95c20f3ffba47130f14a785f9392ce67d96 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Mon, 6 Jun 2022 16:36:04 +0800 Subject: [PATCH 05/15] fix: activate the correct tab index after renaming a Tabs name (#103) --- src/store/modules/demand-log.ts | 1 - src/views/dashboard/panel/Layout.vue | 9 ++++++--- src/views/dashboard/panel/Tool.vue | 2 +- .../dashboard/related/ebpf/components/EBPFSchedules.vue | 7 ++++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/store/modules/demand-log.ts b/src/store/modules/demand-log.ts index 0eaf7d1e..87565091 100644 --- a/src/store/modules/demand-log.ts +++ b/src/store/modules/demand-log.ts @@ -1,4 +1,3 @@ -import { ElMessage } from "element-plus"; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with diff --git a/src/views/dashboard/panel/Layout.vue b/src/views/dashboard/panel/Layout.vue index b54a23f2..02b93fea 100644 --- a/src/views/dashboard/panel/Layout.vue +++ b/src/views/dashboard/panel/Layout.vue @@ -29,7 +29,7 @@ limitations under the License. --> :h="item.h" :i="item.i" :key="item.i" - @click="clickGrid(item)" + @click="clickGrid(item, $event)" :class="{ active: dashboardStore.activedGridItem === item.i }" :drag-ignore-from="dragIgnoreFrom" > @@ -55,10 +55,13 @@ export default defineComponent({ const dashboardStore = useDashboardStore(); const selectorStore = useSelectorStore(); - function clickGrid(item: LayoutConfig) { + function clickGrid(item: LayoutConfig, event: Event) { dashboardStore.activeGridItem(item.i); dashboardStore.selectWidget(item); - if (item.type === "Tab") { + if ( + item.type === "Tab" && + (event.target as HTMLDivElement)?.className !== "tab-layout" + ) { dashboardStore.setActiveTabIndex(0); } } diff --git a/src/views/dashboard/panel/Tool.vue b/src/views/dashboard/panel/Tool.vue index 8d8ed374..31ea55b5 100644 --- a/src/views/dashboard/panel/Tool.vue +++ b/src/views/dashboard/panel/Tool.vue @@ -80,7 +80,7 @@ limitations under the License. -->
- + diff --git a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue index 21539fd1..0cd8f9d0 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue @@ -40,7 +40,12 @@ limitations under the License. --> @change="changeAggregateType" class="selector mr-10" /> - + diff --git a/src/views/dashboard/related/components/LogTable/LogBrowser.vue b/src/views/dashboard/related/components/LogTable/LogBrowser.vue index 6a1159ef..0b150477 100644 --- a/src/views/dashboard/related/components/LogTable/LogBrowser.vue +++ b/src/views/dashboard/related/components/LogTable/LogBrowser.vue @@ -14,18 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. -->