mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 08:25:25 +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,
|
||||
HeatmapSeriesOption,
|
||||
PieSeriesOption,
|
||||
SankeySeriesOption,
|
||||
} from "echarts/charts";
|
||||
import {
|
||||
TitleComponentOption,
|
||||
@ -46,6 +47,7 @@ export type ECOption = echarts.ComposeOption<
|
||||
| LegendComponentOption
|
||||
| HeatmapSeriesOption
|
||||
| PieSeriesOption
|
||||
| SankeySeriesOption
|
||||
>;
|
||||
|
||||
export function useECharts(
|
||||
|
@ -28,8 +28,8 @@ interface MetricVal {
|
||||
[key: string]: { values: { id: string; value: unknown }[] };
|
||||
}
|
||||
interface TopologyState {
|
||||
node: Node | null;
|
||||
call: Call | null;
|
||||
node: Nullable<Node>;
|
||||
call: Nullable<Call>;
|
||||
calls: Call[];
|
||||
nodes: Node[];
|
||||
nodeMetrics: MetricVal;
|
||||
@ -76,6 +76,43 @@ export const topologyStore = defineStore({
|
||||
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 }[]) {
|
||||
this.nodeMetrics = m;
|
||||
},
|
||||
@ -135,7 +172,7 @@ export const topologyStore = defineStore({
|
||||
duration,
|
||||
});
|
||||
if (!res.data.errors) {
|
||||
this.setTopology(res.data.data.topology);
|
||||
this.setEndpointTopology(res.data.data.topology);
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
@ -151,7 +188,7 @@ export const topologyStore = defineStore({
|
||||
duration,
|
||||
});
|
||||
if (!res.data.errors) {
|
||||
this.setTopology(res.data.data.topology);
|
||||
this.setInstanceTopology(res.data.data.topology);
|
||||
}
|
||||
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;
|
||||
detectPoints: string[];
|
||||
type?: string;
|
||||
layer?: string;
|
||||
sourceObj?: any;
|
||||
targetObj?: any;
|
||||
value?: number;
|
||||
}
|
||||
export interface Node {
|
||||
id: string;
|
||||
|
@ -16,7 +16,13 @@
|
||||
*/
|
||||
import * as echarts from "echarts/core";
|
||||
|
||||
import { BarChart, LineChart, PieChart, HeatmapChart } from "echarts/charts";
|
||||
import {
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
HeatmapChart,
|
||||
SankeyChart,
|
||||
} from "echarts/charts";
|
||||
|
||||
import {
|
||||
TitleComponent,
|
||||
@ -39,6 +45,7 @@ echarts.use([
|
||||
LineChart,
|
||||
PieChart,
|
||||
HeatmapChart,
|
||||
SankeyChart,
|
||||
SVGRenderer,
|
||||
DataZoomComponent,
|
||||
VisualMapComponent,
|
||||
|
@ -32,7 +32,7 @@ limitations under the License. -->
|
||||
@closed="dashboardStore.setTopology(false)"
|
||||
custom-class="dark-dialog"
|
||||
>
|
||||
<Graph />
|
||||
<Topology />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@ -42,7 +42,7 @@ import GridLayout from "./panel/Layout.vue";
|
||||
// import { LayoutConfig } from "@/types/dashboard";
|
||||
import Tool from "./panel/Tool.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 { useAppStoreWithOut } from "@/store/modules/app";
|
||||
|
||||
|
@ -17,7 +17,7 @@ limitations under the License. -->
|
||||
ref="chart"
|
||||
class="micro-topo-chart"
|
||||
v-loading="loading"
|
||||
:style="`height: ${height}`"
|
||||
:style="`height: ${height}px`"
|
||||
>
|
||||
<div class="setting" v-show="showSetting">
|
||||
<Settings @update="updateSettings" />
|
||||
@ -182,7 +182,6 @@ function handleNodeClick(d: Node & { x: number; y: number }) {
|
||||
topologyStore.setLink(null);
|
||||
operationsPos.x = d.x;
|
||||
operationsPos.y = d.y + 30;
|
||||
console.log(d.layer === String(dashboardStore.layerId));
|
||||
if (d.layer === String(dashboardStore.layerId)) {
|
||||
return;
|
||||
}
|
||||
@ -322,7 +321,9 @@ async function handleInspect() {
|
||||
const id = topologyStore.node.id;
|
||||
topologyStore.setNode(null);
|
||||
topologyStore.setLink(null);
|
||||
loading.value = true;
|
||||
const resp = await topologyStore.getServiceTopology(id);
|
||||
loading.value = false;
|
||||
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
@ -356,7 +357,9 @@ function handleGoAlarm() {
|
||||
}
|
||||
async function backToTopology() {
|
||||
svg.value.selectAll(".topo-svg-graph").remove();
|
||||
loading.value = true;
|
||||
const resp = await getTopology();
|
||||
loading.value = false;
|
||||
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
@ -377,12 +380,6 @@ async function getTopology() {
|
||||
case EntityType[1].value:
|
||||
resp = await topologyStore.getServicesTopology();
|
||||
break;
|
||||
case EntityType[2].value:
|
||||
resp = await topologyStore.getEndpointTopology();
|
||||
break;
|
||||
case EntityType[4].value:
|
||||
resp = await topologyStore.getInstanceTopology();
|
||||
break;
|
||||
}
|
||||
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