mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 11:25:22 +00:00
feat: add graph for instance and endpoint topology
This commit is contained in:
parent
41ad830ea8
commit
06a4a4ac96
@ -19,6 +19,7 @@ import {
|
|||||||
LineSeriesOption,
|
LineSeriesOption,
|
||||||
HeatmapSeriesOption,
|
HeatmapSeriesOption,
|
||||||
PieSeriesOption,
|
PieSeriesOption,
|
||||||
|
SankeySeriesOption,
|
||||||
} from "echarts/charts";
|
} from "echarts/charts";
|
||||||
import {
|
import {
|
||||||
TitleComponentOption,
|
TitleComponentOption,
|
||||||
@ -46,6 +47,7 @@ export type ECOption = echarts.ComposeOption<
|
|||||||
| LegendComponentOption
|
| LegendComponentOption
|
||||||
| HeatmapSeriesOption
|
| HeatmapSeriesOption
|
||||||
| PieSeriesOption
|
| PieSeriesOption
|
||||||
|
| SankeySeriesOption
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function useECharts(
|
export function useECharts(
|
||||||
|
@ -28,8 +28,8 @@ interface MetricVal {
|
|||||||
[key: string]: { values: { id: string; value: unknown }[] };
|
[key: string]: { values: { id: string; value: unknown }[] };
|
||||||
}
|
}
|
||||||
interface TopologyState {
|
interface TopologyState {
|
||||||
node: Node | null;
|
node: Nullable<Node>;
|
||||||
call: Call | null;
|
call: Nullable<Call>;
|
||||||
calls: Call[];
|
calls: Call[];
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
nodeMetrics: MetricVal;
|
nodeMetrics: MetricVal;
|
||||||
@ -76,6 +76,43 @@ export const topologyStore = defineStore({
|
|||||||
return c;
|
return c;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setInstanceTopology(data: { nodes: Node[]; calls: Call[] }) {
|
||||||
|
for (const call of data.calls) {
|
||||||
|
for (const node of data.nodes) {
|
||||||
|
if (call.source === node.id) {
|
||||||
|
call.sourceObj = node;
|
||||||
|
}
|
||||||
|
if (call.target === node.id) {
|
||||||
|
call.targetObj = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
call.value = call.value || 1;
|
||||||
|
}
|
||||||
|
this.calls = data.calls;
|
||||||
|
this.nodes = data.nodes;
|
||||||
|
},
|
||||||
|
setEndpointTopology(data: { nodes: Node[]; calls: Call[] }) {
|
||||||
|
const obj = {} as any;
|
||||||
|
let nodes = [];
|
||||||
|
let calls = [];
|
||||||
|
nodes = data.nodes.reduce((prev: Node[], next: Node) => {
|
||||||
|
if (!obj[next.id]) {
|
||||||
|
obj[next.id] = true;
|
||||||
|
prev.push(next);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
calls = data.calls.reduce((prev: Call[], next: Call) => {
|
||||||
|
if (!obj[next.id]) {
|
||||||
|
obj[next.id] = true;
|
||||||
|
next.value = next.value || 1;
|
||||||
|
prev.push(next);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
this.calls = calls;
|
||||||
|
this.nodes = nodes;
|
||||||
|
},
|
||||||
setNodeMetrics(m: { id: string; value: unknown }[]) {
|
setNodeMetrics(m: { id: string; value: unknown }[]) {
|
||||||
this.nodeMetrics = m;
|
this.nodeMetrics = m;
|
||||||
},
|
},
|
||||||
@ -135,7 +172,7 @@ export const topologyStore = defineStore({
|
|||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
this.setTopology(res.data.data.topology);
|
this.setEndpointTopology(res.data.data.topology);
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
@ -151,7 +188,7 @@ export const topologyStore = defineStore({
|
|||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
this.setTopology(res.data.data.topology);
|
this.setInstanceTopology(res.data.data.topology);
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
4
src/types/topology.d.ts
vendored
4
src/types/topology.d.ts
vendored
@ -20,7 +20,9 @@ export interface Call {
|
|||||||
id: string;
|
id: string;
|
||||||
detectPoints: string[];
|
detectPoints: string[];
|
||||||
type?: string;
|
type?: string;
|
||||||
layer?: string;
|
sourceObj?: any;
|
||||||
|
targetObj?: any;
|
||||||
|
value?: number;
|
||||||
}
|
}
|
||||||
export interface Node {
|
export interface Node {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -16,7 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
import * as echarts from "echarts/core";
|
import * as echarts from "echarts/core";
|
||||||
|
|
||||||
import { BarChart, LineChart, PieChart, HeatmapChart } from "echarts/charts";
|
import {
|
||||||
|
BarChart,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
HeatmapChart,
|
||||||
|
SankeyChart,
|
||||||
|
} from "echarts/charts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
@ -39,6 +45,7 @@ echarts.use([
|
|||||||
LineChart,
|
LineChart,
|
||||||
PieChart,
|
PieChart,
|
||||||
HeatmapChart,
|
HeatmapChart,
|
||||||
|
SankeyChart,
|
||||||
SVGRenderer,
|
SVGRenderer,
|
||||||
DataZoomComponent,
|
DataZoomComponent,
|
||||||
VisualMapComponent,
|
VisualMapComponent,
|
||||||
|
@ -32,7 +32,7 @@ limitations under the License. -->
|
|||||||
@closed="dashboardStore.setTopology(false)"
|
@closed="dashboardStore.setTopology(false)"
|
||||||
custom-class="dark-dialog"
|
custom-class="dark-dialog"
|
||||||
>
|
>
|
||||||
<Graph />
|
<Topology />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -42,7 +42,7 @@ import GridLayout from "./panel/Layout.vue";
|
|||||||
// import { LayoutConfig } from "@/types/dashboard";
|
// import { LayoutConfig } from "@/types/dashboard";
|
||||||
import Tool from "./panel/Tool.vue";
|
import Tool from "./panel/Tool.vue";
|
||||||
import ConfigEdit from "./configuration/ConfigEdit.vue";
|
import ConfigEdit from "./configuration/ConfigEdit.vue";
|
||||||
import Graph from "./related/topology/Graph.vue";
|
import Topology from "./related/topology/Index.vue";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License. -->
|
|||||||
ref="chart"
|
ref="chart"
|
||||||
class="micro-topo-chart"
|
class="micro-topo-chart"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:style="`height: ${height}`"
|
:style="`height: ${height}px`"
|
||||||
>
|
>
|
||||||
<div class="setting" v-show="showSetting">
|
<div class="setting" v-show="showSetting">
|
||||||
<Settings @update="updateSettings" />
|
<Settings @update="updateSettings" />
|
||||||
@ -182,7 +182,6 @@ function handleNodeClick(d: Node & { x: number; y: number }) {
|
|||||||
topologyStore.setLink(null);
|
topologyStore.setLink(null);
|
||||||
operationsPos.x = d.x;
|
operationsPos.x = d.x;
|
||||||
operationsPos.y = d.y + 30;
|
operationsPos.y = d.y + 30;
|
||||||
console.log(d.layer === String(dashboardStore.layerId));
|
|
||||||
if (d.layer === String(dashboardStore.layerId)) {
|
if (d.layer === String(dashboardStore.layerId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -322,7 +321,9 @@ async function handleInspect() {
|
|||||||
const id = topologyStore.node.id;
|
const id = topologyStore.node.id;
|
||||||
topologyStore.setNode(null);
|
topologyStore.setNode(null);
|
||||||
topologyStore.setLink(null);
|
topologyStore.setLink(null);
|
||||||
|
loading.value = true;
|
||||||
const resp = await topologyStore.getServiceTopology(id);
|
const resp = await topologyStore.getServiceTopology(id);
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
if (resp.errors) {
|
if (resp.errors) {
|
||||||
ElMessage.error(resp.errors);
|
ElMessage.error(resp.errors);
|
||||||
@ -356,7 +357,9 @@ function handleGoAlarm() {
|
|||||||
}
|
}
|
||||||
async function backToTopology() {
|
async function backToTopology() {
|
||||||
svg.value.selectAll(".topo-svg-graph").remove();
|
svg.value.selectAll(".topo-svg-graph").remove();
|
||||||
|
loading.value = true;
|
||||||
const resp = await getTopology();
|
const resp = await getTopology();
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
if (resp.errors) {
|
if (resp.errors) {
|
||||||
ElMessage.error(resp.errors);
|
ElMessage.error(resp.errors);
|
||||||
@ -377,12 +380,6 @@ async function getTopology() {
|
|||||||
case EntityType[1].value:
|
case EntityType[1].value:
|
||||||
resp = await topologyStore.getServicesTopology();
|
resp = await topologyStore.getServicesTopology();
|
||||||
break;
|
break;
|
||||||
case EntityType[2].value:
|
|
||||||
resp = await topologyStore.getEndpointTopology();
|
|
||||||
break;
|
|
||||||
case EntityType[4].value:
|
|
||||||
resp = await topologyStore.getInstanceTopology();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
34
src/views/dashboard/related/topology/Index.vue
Normal file
34
src/views/dashboard/related/topology/Index.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!-- 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. -->
|
||||||
|
<template>
|
||||||
|
<div :style="`height:${height}px;width:${width}px;`" v-if="isSankey">
|
||||||
|
<Sankey />
|
||||||
|
</div>
|
||||||
|
<Graph v-else />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import Graph from "./Graph.vue";
|
||||||
|
import Sankey from "./Sankey.vue";
|
||||||
|
import { EntityType } from "../../data";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const isSankey = ref<boolean>(
|
||||||
|
[EntityType[2].value, EntityType[4].value].includes(dashboardStore.entity)
|
||||||
|
);
|
||||||
|
const height = ref<number>(document.body.clientHeight - 90);
|
||||||
|
const width = ref<number>(document.body.clientWidth - 40);
|
||||||
|
</script>
|
107
src/views/dashboard/related/topology/Sankey.vue
Normal file
107
src/views/dashboard/related/topology/Sankey.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<!-- 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. -->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Graph :option="option" v-loading="loading" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
import { EntityType } from "../../data";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const option = computed(() => getOption());
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await getTopology();
|
||||||
|
loading.value = false;
|
||||||
|
if (resp && resp.errors) {
|
||||||
|
ElMessage.error(resp.errors);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getOption() {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item",
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
type: "sankey",
|
||||||
|
left: 40,
|
||||||
|
top: 20,
|
||||||
|
right: 300,
|
||||||
|
bottom: 40,
|
||||||
|
emphasis: { focus: "adjacency" },
|
||||||
|
data: topologyStore.nodes,
|
||||||
|
links: topologyStore.calls,
|
||||||
|
label: {
|
||||||
|
color: "#fff",
|
||||||
|
formatter: (param: any) => param.data.name,
|
||||||
|
},
|
||||||
|
color: [
|
||||||
|
"#3fe1da",
|
||||||
|
"#6be6c1",
|
||||||
|
"#3fcfdc",
|
||||||
|
"#626c91",
|
||||||
|
"#3fbcde",
|
||||||
|
"#a0a7e6",
|
||||||
|
"#3fa9e1",
|
||||||
|
"#96dee8",
|
||||||
|
"#bf99f8",
|
||||||
|
],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: "source",
|
||||||
|
opacity: 0.12,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
position: "bottom",
|
||||||
|
formatter: (param: { data: any; dataType: string }) => {
|
||||||
|
if (param.dataType === "edge") {
|
||||||
|
return `${param.data.sourceObj.serviceName} -> ${param.data.targetObj.serviceName}`;
|
||||||
|
}
|
||||||
|
return param.data.serviceName;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTopology() {
|
||||||
|
let resp;
|
||||||
|
switch (dashboardStore.entity) {
|
||||||
|
case EntityType[2].value:
|
||||||
|
resp = await topologyStore.getEndpointTopology();
|
||||||
|
break;
|
||||||
|
case EntityType[4].value:
|
||||||
|
resp = await topologyStore.getInstanceTopology();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sankey {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user