From f9aacb72e11e844010f0fdbf8198b3197534c959 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Tue, 22 Mar 2022 13:40:40 +0800 Subject: [PATCH] feat: save and init topology templates (#32) --- src/locales/lang/zh.ts | 2 +- src/store/modules/topology.ts | 57 ++++++++-- src/views/dashboard/List.vue | 4 +- .../configuration/widget/MetricOptions.vue | 10 +- src/views/dashboard/graphs/EndpointList.vue | 2 +- src/views/dashboard/graphs/InstanceList.vue | 2 +- .../dashboard/related/topology/Index.vue | 9 +- .../related/topology/components/Graph.vue | 46 +++++--- .../topology/components/PodTopology.vue | 30 ++++-- .../related/topology/components/Sankey.vue | 4 +- .../related/topology/components/Settings.vue | 101 ++++++++---------- 11 files changed, 169 insertions(+), 98 deletions(-) diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts index 445c5c58..a4acc1f2 100644 --- a/src/locales/lang/zh.ts +++ b/src/locales/lang/zh.ts @@ -115,7 +115,7 @@ const msg = { rename: "重命名", selfObservability: "自观性", satellite: "Satellite", - skyWalkingServer: "Sky Walking服务", + skyWalkingServer: "SkyWalking服务", functions: "Functions", linux: "Linux", browser: "浏览器", diff --git a/src/store/modules/topology.ts b/src/store/modules/topology.ts index 9de0316e..32da7b3f 100644 --- a/src/store/modules/topology.ts +++ b/src/store/modules/topology.ts @@ -23,6 +23,8 @@ import { useSelectorStore } from "@/store/modules/selectors"; import { useAppStoreWithOut } from "@/store/modules/app"; import { AxiosResponse } from "axios"; import query from "@/graphql/fetch"; +import { useQueryTopologyMetrics } from "@/hooks/useProcessor"; +import { ElMessage } from "element-plus"; interface MetricVal { [key: string]: { values: { id: string; value: unknown }[] }; @@ -32,7 +34,7 @@ interface TopologyState { call: Nullable; calls: Call[]; nodes: Node[]; - nodeMetrics: MetricVal; + nodeMetricValue: MetricVal; linkServerMetrics: MetricVal; linkClientMetrics: MetricVal; } @@ -44,7 +46,7 @@ export const topologyStore = defineStore({ nodes: [], node: null, call: null, - nodeMetrics: {}, + nodeMetricValue: {}, linkServerMetrics: {}, linkClientMetrics: {}, }), @@ -103,8 +105,8 @@ export const topologyStore = defineStore({ this.calls = calls; this.nodes = nodes; }, - setNodeMetrics(m: { id: string; value: unknown }[]) { - this.nodeMetrics = m; + setNodeMetricValue(m: { id: string; value: unknown }[]) { + this.nodeMetricValue = m; }, setLinkServerMetrics(m: { id: string; value: unknown }[]) { this.linkServerMetrics = m; @@ -388,7 +390,7 @@ export const topologyStore = defineStore({ return { calls, nodes }; }, - async getNodeMetrics(param: { + async getNodeMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown }; }) { @@ -397,9 +399,52 @@ export const topologyStore = defineStore({ if (res.data.errors) { return res.data; } - this.setNodeMetrics(res.data.data); + this.setNodeMetricValue(res.data.data); return res.data; }, + async getLinkClientMetrics(linkClientMetrics: string[]) { + if (!linkClientMetrics.length) { + this.setLinkClientMetrics({}); + return; + } + const idsC = this.calls + .filter((i: Call) => i.detectPoints.includes("CLIENT")) + .map((b: Call) => b.id); + const param = await useQueryTopologyMetrics(linkClientMetrics, idsC); + const res = await this.getCallClientMetrics(param); + + if (res.errors) { + ElMessage.error(res.errors); + } + }, + async getLinkServerMetrics(linkServerMetrics: string[]) { + if (!linkServerMetrics.length) { + this.setLinkServerMetrics({}); + return; + } + const idsS = this.calls + .filter((i: Call) => i.detectPoints.includes("SERVER")) + .map((b: Call) => b.id); + const param = await useQueryTopologyMetrics(linkServerMetrics, idsS); + const res = await this.getCallServerMetrics(param); + + if (res.errors) { + ElMessage.error(res.errors); + } + }, + async queryNodeMetrics(nodeMetrics: string[]) { + if (!nodeMetrics.length) { + this.setNodeMetricValue({}); + return; + } + const ids = this.nodes.map((d: Node) => d.id); + const param = await useQueryTopologyMetrics(nodeMetrics, ids); + const res = await this.getNodeMetricValue(param); + + if (res.errors) { + ElMessage.error(res.errors); + } + }, async getLegendMetrics(param: { queryStr: string; conditions: { [key: string]: unknown }; diff --git a/src/views/dashboard/List.vue b/src/views/dashboard/List.vue index f767b2b9..9211c0cd 100644 --- a/src/views/dashboard/List.vue +++ b/src/views/dashboard/List.vue @@ -46,7 +46,9 @@ limitations under the License. --> diff --git a/src/views/dashboard/configuration/widget/MetricOptions.vue b/src/views/dashboard/configuration/widget/MetricOptions.vue index d41a402b..12d71203 100644 --- a/src/views/dashboard/configuration/widget/MetricOptions.vue +++ b/src/views/dashboard/configuration/widget/MetricOptions.vue @@ -53,8 +53,9 @@ limitations under the License. --> > diff --git a/src/views/dashboard/related/topology/components/Graph.vue b/src/views/dashboard/related/topology/components/Graph.vue index 3f1baf67..8389f8ba 100644 --- a/src/views/dashboard/related/topology/components/Graph.vue +++ b/src/views/dashboard/related/topology/components/Graph.vue @@ -20,7 +20,7 @@ limitations under the License. --> element-loading-background="rgba(0, 0, 0, 0)" :style="`height: ${height}px`" > -
+
@@ -34,7 +34,12 @@ limitations under the License. --> @change="changeDepth" /> - + (null); const arrow = ref(null); const legend = ref(null); const showSetting = ref(false); -const settings = ref({}); +const settings = ref(props.config); const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN }); const items = ref< { id: string; title: string; func: any; dashboard?: string }[] ->([ - { id: "inspect", title: "Inspect", func: handleInspect }, - { id: "alarm", title: "Alarm", func: handleGoAlarm }, -]); +>([]); const depth = ref(props.config.graph.depth || 2); onMounted(async () => { @@ -130,6 +132,9 @@ onMounted(async () => { if (resp && resp.errors) { ElMessage.error(resp.errors); } + topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []); + topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []); + topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []); const dom = document.querySelector(".topology")?.getBoundingClientRect() || { height: 40, width: 0, @@ -140,6 +145,7 @@ onMounted(async () => { svg.value = d3.select(chart.value).append("svg").attr("class", "topo-svg"); await init(); update(); + setNodeTools(settings.value.nodeDashboard); }); async function init() { tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]); @@ -167,6 +173,7 @@ async function init() { event.preventDefault(); topologyStore.setNode(null); topologyStore.setLink(null); + dashboardStore.selectWidget(props.config); }); } function ticked() { @@ -236,6 +243,7 @@ function handleLinkClick(event: any, d: Call) { if (!settings.value.linkDashboard) { return; } + const origin = dashboardStore.entity; const e = dashboardStore.entity === EntityType[1].value ? EntityType[0].value @@ -251,6 +259,7 @@ function handleLinkClick(event: any, d: Call) { }/${p.name.split(" ").join("-")}`; const routeUrl = router.resolve({ path }); window.open(routeUrl.href, "_blank"); + dashboardStore.setEntity(origin); } function update() { // node element @@ -271,7 +280,7 @@ function update() { const nodeMetrics: string[] = settings.value.nodeMetrics || []; const html = nodeMetrics.map((m) => { const metric = - topologyStore.nodeMetrics[m].values.filter( + topologyStore.nodeMetricValue[m].values.filter( (val: { id: string; value: unknown }) => val.id === data.id )[0] || {}; const val = m.includes("_sla") ? metric.value / 100 : metric.value; @@ -380,6 +389,7 @@ async function handleInspect() { update(); } function handleGoEndpoint(name: string) { + const origin = dashboardStore.entity; const p = getDashboard({ name, layer: dashboardStore.layerId, @@ -392,8 +402,10 @@ function handleGoEndpoint(name: string) { const routeUrl = router.resolve({ path }); window.open(routeUrl.href, "_blank"); + dashboardStore.setEntity(origin); } function handleGoInstance(name: string) { + const origin = dashboardStore.entity; const p = getDashboard({ name, layer: dashboardStore.layerId, @@ -406,8 +418,10 @@ function handleGoInstance(name: string) { const routeUrl = router.resolve({ path }); window.open(routeUrl.href, "_blank"); + dashboardStore.setEntity(origin); } function handleGoDashboard(name: string) { + const origin = dashboardStore.entity; const p = getDashboard({ name, layer: dashboardStore.layerId, @@ -420,6 +434,7 @@ function handleGoDashboard(name: string) { const routeUrl = router.resolve({ path }); window.open(routeUrl.href, "_blank"); + dashboardStore.setEntity(origin); } function handleGoAlarm() { const path = `/alarm`; @@ -455,6 +470,7 @@ async function getTopology() { } function setConfig() { showSetting.value = !showSetting.value; + dashboardStore.selectWidget(props.config); } function resize() { height.value = document.body.clientHeight; @@ -462,16 +478,19 @@ function resize() { svg.value.attr("height", height.value).attr("width", width.value); } function updateSettings(config: any) { + settings.value = config; + setNodeTools(config.nodeDashboard); +} +function setNodeTools(nodeDashboard: any) { items.value = [ { id: "inspect", title: "Inspect", func: handleInspect }, { id: "alarm", title: "Alarm", func: handleGoAlarm }, ]; - settings.value = config; - for (const item of config.nodeDashboard) { + for (const item of nodeDashboard) { if (item.scope === EntityType[0].value) { items.value.push({ id: "dashboard", - title: "Dashboard", + title: "Service Dashboard", func: handleGoDashboard, ...item, }); @@ -479,7 +498,7 @@ function updateSettings(config: any) { if (item.scope === EntityType[2].value) { items.value.push({ id: "endpoint", - title: "Endpoint", + title: "Endpoint Dashboard", func: handleGoEndpoint, ...item, }); @@ -487,7 +506,7 @@ function updateSettings(config: any) { if (item.scope === EntityType[3].value) { items.value.push({ id: "instance", - title: "Service Instance", + title: "Service Instance Dashboard", func: handleGoInstance, ...item, }); @@ -567,7 +586,6 @@ watch( span { display: block; height: 30px; - width: 140px; line-height: 30px; text-align: center; } diff --git a/src/views/dashboard/related/topology/components/PodTopology.vue b/src/views/dashboard/related/topology/components/PodTopology.vue index 83e9fbf4..c7119976 100644 --- a/src/views/dashboard/related/topology/components/PodTopology.vue +++ b/src/views/dashboard/related/topology/components/PodTopology.vue @@ -28,7 +28,12 @@ limitations under the License. --> @change="changeDepth" /> - + > -
+
@@ -104,7 +109,7 @@ const loading = ref(false); const height = ref(100); const width = ref(100); const showSettings = ref(false); -const settings = ref({}); +const settings = ref(props.config); const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN }); const depth = ref(props.config.graph.depth || 3); const items = [ @@ -130,6 +135,9 @@ async function loadTopology(id: string) { }; height.value = dom.height - 70; width.value = dom.width - 5; + topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []); + topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []); + topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []); } function inspect() { @@ -158,7 +166,9 @@ function goDashboard() { }); dashboardStore.setEntity(entity); dashboardStore.setCurrentDashboard(d); - const path = `/dashboard/${d.layer}/${entity}/${topologyStore.node.serviceId}/${topologyStore.node.id}/${d.name}`; + const path = `/dashboard/${d.layer}/${entity}/${ + topologyStore.node.serviceId + }/${topologyStore.node.id}/${d.name.split(" ").join("-")}`; const routeUrl = router.resolve({ path }); window.open(routeUrl.href, "_blank"); topologyStore.setNode(null); @@ -167,6 +177,7 @@ function goDashboard() { function setConfig() { topologyStore.setNode(null); showSettings.value = !showSettings.value; + dashboardStore.selectWidget(props.config); } function updateConfig(config: any) { @@ -196,7 +207,9 @@ function selectNodeLink(d: any) { entity, }); dashboardStore.setEntity(entity); - const path = `/dashboard/${p.layer}/${entity}/${sourceObj.serviceId}/${sourceObj.id}/${targetObj.serviceId}/${targetObj.id}/${p.name}`; + const path = `/dashboard/${p.layer}/${entity}/${sourceObj.serviceId}/${ + sourceObj.id + }/${targetObj.serviceId}/${targetObj.id}/${p.name.split(" ").join("-")}`; const routeUrl = router.resolve({ path }); window.open(routeUrl.href, "_blank"); return; @@ -221,7 +234,7 @@ async function getTopology(id: string) { Number(depth.value) ); break; - case EntityType[3].value: + case EntityType[4].value: resp = await topologyStore.getInstanceTopology(); break; } @@ -231,6 +244,7 @@ function handleClick(event: any) { if (event.target.nodeName === "svg") { topologyStore.setNode(null); topologyStore.setLink(null); + dashboardStore.selectWidget(props.config); } } watch( @@ -262,10 +276,10 @@ watch( top: 60px; right: 10px; width: 400px; - height: 500px; + height: 600px; background-color: #2b3037; overflow: auto; - padding: 0 15px; + padding: 10px 15px; border-radius: 3px; color: #ccc; transition: all 0.5ms linear; diff --git a/src/views/dashboard/related/topology/components/Sankey.vue b/src/views/dashboard/related/topology/components/Sankey.vue index 4aa6a656..47b3ccab 100644 --- a/src/views/dashboard/related/topology/components/Sankey.vue +++ b/src/views/dashboard/related/topology/components/Sankey.vue @@ -105,10 +105,10 @@ function linkTooltip(data: Call) { } function nodeTooltip(data: Node) { - const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetrics); + const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetricValue); const html = nodeMetrics.map((m) => { const metric = - topologyStore.nodeMetrics[m].values.filter( + topologyStore.nodeMetricValue[m].values.filter( (val: { id: string; value: unknown }) => val.id === data.id )[0] || {}; const val = m.includes("_sla") ? metric.value / 100 : metric.value; diff --git a/src/views/dashboard/related/topology/components/Settings.vue b/src/views/dashboard/related/topology/components/Settings.vue index 55895ab4..8dbe619f 100644 --- a/src/views/dashboard/related/topology/components/Settings.vue +++ b/src/views/dashboard/related/topology/components/Settings.vue @@ -186,7 +186,7 @@ import { ElMessage } from "element-plus"; import { MetricCatalog, ScopeType, MetricConditions } from "../../../data"; import { Option } from "@/types/app"; import { useQueryTopologyMetrics } from "@/hooks/useProcessor"; -import { Node, Call } from "@/types/topology"; +import { Node } from "@/types/topology"; import { DashboardItem } from "@/types/dashboard"; import { EntityType, LegendOpt, MetricsType } from "../../../data"; @@ -195,12 +195,16 @@ const emit = defineEmits(["update", "updateNodes"]); const { t } = useI18n(); const dashboardStore = useDashboardStore(); const topologyStore = useTopologyStore(); +const { selectedGrid } = dashboardStore; +const isService = [EntityType[0].value, EntityType[1].value].includes( + dashboardStore.entity +); const items = reactive< { scope: string; dashboard: string; }[] ->([{ scope: "", dashboard: "" }]); +>((isService && selectedGrid.nodeDashboard) || [{ scope: "", dashboard: "" }]); const states = reactive<{ linkDashboard: string; nodeDashboard: { @@ -215,23 +219,19 @@ const states = reactive<{ linkDashboards: (DashboardItem & { label: string; value: string })[]; nodeDashboards: (DashboardItem & { label: string; value: string })[]; }>({ - linkDashboard: "", - nodeDashboard: [], - linkServerMetrics: [], - linkClientMetrics: [], - nodeMetrics: [], + linkDashboard: selectedGrid.linkDashboard || "", + nodeDashboard: selectedGrid.nodeDashboard || [], + linkServerMetrics: selectedGrid.linkServerMetrics || [], + linkClientMetrics: selectedGrid.linkClientMetrics || [], + nodeMetrics: selectedGrid.nodeMetrics || [], nodeMetricList: [], linkMetricList: [], linkDashboards: [], nodeDashboards: [], }); -console.log(dashboardStore.selectedGrid); -const isService = [EntityType[0].value, EntityType[1].value].includes( - dashboardStore.entity -); const legend = reactive<{ metric: { name: string; condition: string; value: string }[]; -}>({ metric: [{ name: "", condition: "", value: "" }] }); +}>({ metric: selectedGrid.legend || [{ name: "", condition: "", value: "" }] }); getMetricList(); async function getMetricList() { @@ -270,24 +270,26 @@ async function getMetricList() { entity + "Relation" === (MetricCatalog as any)[d.catalog] && d.type === MetricsType.REGULAR_VALUE ); + if (isService) { + return; + } + states.nodeDashboards = list.reduce( + ( + prev: (DashboardItem & { label: string; value: string })[], + d: DashboardItem + ) => { + if (d.layer === dashboardStore.layerId && d.entity === entity) { + prev.push({ ...d, label: d.name, value: d.name }); + } + return prev; + }, + [] + ); } async function setLegend() { - const metrics = legend.metric.filter( - (d: any) => d.name && d.value && d.condition - ); - const names = metrics.map((d: any) => d.name); - - emit("update", { - linkDashboard: states.linkDashboard, - nodeDashboard: isService - ? items.filter((d: { scope: string; dashboard: string }) => d.dashboard) - : states.nodeDashboard, - linkServerMetrics: states.linkServerMetrics, - linkClientMetrics: states.linkClientMetrics, - nodeMetrics: states.nodeMetrics, - legend: metrics, - }); + updateSettings(); const ids = topologyStore.nodes.map((d: Node) => d.id); + const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name); const param = await useQueryTopologyMetrics(names, ids); const res = await topologyStore.getLegendMetrics(param); @@ -298,9 +300,11 @@ async function setLegend() { } function changeNodeDashboard(opt: any) { states.nodeDashboard = opt[0].value; + updateSettings(); } function changeLinkDashboard(opt: any) { states.linkDashboard = opt[0].value; + updateSettings(); } function changeLegend(type: string, opt: any, index: number) { (legend.metric[index] as any)[type] = opt[0].value || opt; @@ -321,6 +325,7 @@ function changeScope(index: number, opt: Option[] | any) { [] ); items[index].dashboard = states.nodeDashboards[0].value; + updateSettings(); } function updateNodeDashboards(index: number, content: Option[] | any) { items[index].dashboard = content[0].value; @@ -334,7 +339,10 @@ function deleteItem(index: number) { updateSettings(); } function updateSettings() { - emit("update", { + const metrics = legend.metric.filter( + (d: any) => d.name && d.value && d.condition + ); + const param = { linkDashboard: states.linkDashboard, nodeDashboard: isService ? items.filter((d: { scope: string; dashboard: string }) => d.dashboard) @@ -342,8 +350,11 @@ function updateSettings() { linkServerMetrics: states.linkServerMetrics, linkClientMetrics: states.linkClientMetrics, nodeMetrics: states.nodeMetrics, - legend: legend.metric, - }); + legend: metrics, + }; + dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, ...param }); + dashboardStore.setConfigs({ ...dashboardStore.selectedGrid, ...param }); + emit("update", param); } async function changeLinkServerMetrics(options: Option[] | any) { states.linkServerMetrics = options.map((d: Option) => d.value); @@ -352,15 +363,7 @@ async function changeLinkServerMetrics(options: Option[] | any) { topologyStore.setLinkServerMetrics({}); return; } - const idsS = topologyStore.calls - .filter((i: Call) => i.detectPoints.includes("SERVER")) - .map((b: Call) => b.id); - const param = await useQueryTopologyMetrics(states.linkServerMetrics, idsS); - const res = await topologyStore.getCallServerMetrics(param); - - if (res.errors) { - ElMessage.error(res.errors); - } + topologyStore.getLinkServerMetrics(states.linkServerMetrics); } async function changeLinkClientMetrics(options: Option[] | any) { states.linkClientMetrics = options.map((d: Option) => d.value); @@ -369,30 +372,16 @@ async function changeLinkClientMetrics(options: Option[] | any) { topologyStore.setLinkClientMetrics({}); return; } - const idsC = topologyStore.calls - .filter((i: Call) => i.detectPoints.includes("CLIENT")) - .map((b: Call) => b.id); - const param = await useQueryTopologyMetrics(states.linkClientMetrics, idsC); - const res = await topologyStore.getCallClientMetrics(param); - - if (res.errors) { - ElMessage.error(res.errors); - } + topologyStore.getLinkClientMetrics(states.linkClientMetrics); } async function changeNodeMetrics(options: Option[] | any) { states.nodeMetrics = options.map((d: Option) => d.value); updateSettings(); if (!states.nodeMetrics.length) { - topologyStore.setNodeMetrics({}); + topologyStore.setNodeMetricValue({}); return; } - const ids = topologyStore.nodes.map((d: Node) => d.id); - const param = await useQueryTopologyMetrics(states.nodeMetrics, ids); - const res = await topologyStore.getNodeMetrics(param); - - if (res.errors) { - ElMessage.error(res.errors); - } + topologyStore.queryNodeMetrics(states.nodeMetrics); } function deleteMetric(index: number) { legend.metric.splice(index, 1);