mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-12 15:52:57 +00:00
feat: add instance hierarchy topology
This commit is contained in:
parent
b18f206622
commit
0cc7329e5b
@ -1,3 +1,4 @@
|
|||||||
|
import { dashboardStore } from "./dashboard";
|
||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
@ -20,6 +21,7 @@ import type { Service } from "@/types/selector";
|
|||||||
import type { Node, Call, HierarchyNode, ServiceHierarchy, InstanceHierarchy } from "@/types/topology";
|
import type { Node, Call, HierarchyNode, ServiceHierarchy, InstanceHierarchy } from "@/types/topology";
|
||||||
import graphql from "@/graphql";
|
import graphql from "@/graphql";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import query from "@/graphql/fetch";
|
import query from "@/graphql/fetch";
|
||||||
@ -43,6 +45,7 @@ interface TopologyState {
|
|||||||
linkServerMetrics: MetricVal;
|
linkServerMetrics: MetricVal;
|
||||||
linkClientMetrics: MetricVal;
|
linkClientMetrics: MetricVal;
|
||||||
hierarchyNodeMetrics: { [key: string]: MetricVal };
|
hierarchyNodeMetrics: { [key: string]: MetricVal };
|
||||||
|
hierarchyInstanceNodeMetrics: { [key: string]: MetricVal };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const topologyStore = defineStore({
|
export const topologyStore = defineStore({
|
||||||
@ -60,6 +63,7 @@ export const topologyStore = defineStore({
|
|||||||
linkServerMetrics: {},
|
linkServerMetrics: {},
|
||||||
linkClientMetrics: {},
|
linkClientMetrics: {},
|
||||||
hierarchyNodeMetrics: {},
|
hierarchyNodeMetrics: {},
|
||||||
|
hierarchyInstanceNodeMetrics: {},
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
setNode(node: Node) {
|
setNode(node: Node) {
|
||||||
@ -183,6 +187,9 @@ export const topologyStore = defineStore({
|
|||||||
setHierarchyNodeMetricValue(m: MetricVal, layer: string) {
|
setHierarchyNodeMetricValue(m: MetricVal, layer: string) {
|
||||||
this.hierarchyNodeMetrics[layer] = m;
|
this.hierarchyNodeMetrics[layer] = m;
|
||||||
},
|
},
|
||||||
|
setHierarchyInstanceNodeMetricValue(m: MetricVal, layer: string) {
|
||||||
|
this.hierarchyInstanceNodeMetrics[layer] = m;
|
||||||
|
},
|
||||||
setLinkServerMetrics(m: MetricVal) {
|
setLinkServerMetrics(m: MetricVal) {
|
||||||
this.linkServerMetrics = m;
|
this.linkServerMetrics = m;
|
||||||
},
|
},
|
||||||
@ -574,17 +581,33 @@ export const topologyStore = defineStore({
|
|||||||
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {});
|
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {});
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getHierarchyInstanceTopology(params: { instanceId: string; layer: string }) {
|
async getHierarchyInstanceTopology() {
|
||||||
if (!(params.instanceId && params.layer)) {
|
const { currentPod } = useSelectorStore();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
if (!(currentPod && dashboardStore.layerId)) {
|
||||||
return new Promise((resolve) => resolve({}));
|
return new Promise((resolve) => resolve({}));
|
||||||
}
|
}
|
||||||
const res: AxiosResponse = await graphql.query("getHierarchyInstanceTopology").params(params);
|
const res: AxiosResponse = await graphql
|
||||||
|
.query("getHierarchyInstanceTopology")
|
||||||
|
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
|
||||||
if (res.data.errors) {
|
if (res.data.errors) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
this.setInstanceTopology(res.data.data.hierarchyInstanceTopology || {});
|
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {});
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
|
||||||
|
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
|
||||||
|
const param = getExpressionQuery();
|
||||||
|
const res = await this.getNodeExpressionValue(param);
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const metrics = handleExpressionValues(res.data);
|
||||||
|
return metrics;
|
||||||
|
},
|
||||||
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
|
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
|
||||||
const nodes = this.hierarchyServiceNodes.filter((n: Node) => n.layer === layer);
|
const nodes = this.hierarchyServiceNodes.filter((n: Node) => n.layer === layer);
|
||||||
if (!nodes.length) {
|
if (!nodes.length) {
|
||||||
@ -598,15 +621,24 @@ export const topologyStore = defineStore({
|
|||||||
this.setHierarchyNodeMetricValue({}, layer);
|
this.setHierarchyNodeMetricValue({}, layer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
|
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
|
||||||
const param = getExpressionQuery();
|
this.setHierarchyNodeMetricValue(metrics, layer);
|
||||||
const res = await this.getNodeExpressionValue(param);
|
},
|
||||||
if (res.errors) {
|
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
|
||||||
ElMessage.error(res.errors);
|
const nodes = this.hierarchyInstanceNodes.filter((n: Node) => n.layer === layer);
|
||||||
|
if (!nodes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const metrics = handleExpressionValues(res.data);
|
if (!expressions.length) {
|
||||||
this.setHierarchyNodeMetricValue(metrics, layer);
|
this.setHierarchyInstanceNodeMetricValue({}, layer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.hierarchyServiceNodes.length) {
|
||||||
|
this.setHierarchyInstanceNodeMetricValue({}, layer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
|
||||||
|
this.setHierarchyInstanceNodeMetricValue(metrics, layer);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -134,12 +134,15 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog v-model="showHierarchy" :destroy-on-close="true" @closed="showHierarchy = false" width="640px">
|
<el-dialog v-model="showHierarchy" :destroy-on-close="true" @closed="showHierarchy = false" width="640px">
|
||||||
<div class="hierarchy-related"> hierarchy-related </div>
|
<div class="hierarchy-related">
|
||||||
|
<instance-map />
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, computed, watch } from "vue";
|
import { reactive, ref, computed, watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import {
|
import {
|
||||||
@ -158,7 +161,7 @@ limitations under the License. -->
|
|||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import type { Option } from "@/types/app";
|
import type { Option } from "@/types/app";
|
||||||
import { useI18n } from "vue-i18n";
|
import InstanceMap from "@/views/dashboard/related/topology/pod/InstanceMap.vue";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
@ -726,4 +729,10 @@ limitations under the License. -->
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hierarchy-related {
|
||||||
|
height: 400px;
|
||||||
|
width: 600px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -75,13 +75,10 @@ limitations under the License. -->
|
|||||||
import { ref, computed, watch } from "vue";
|
import { ref, computed, watch } from "vue";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import type { Node, Call } from "@/types/topology";
|
import type { Node, Call } from "@/types/topology";
|
||||||
import { useTopologyStore } from "@/store/modules/topology";
|
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
|
|
||||||
import icons from "@/assets/img/icons";
|
import icons from "@/assets/img/icons";
|
||||||
import { layout, changeNode, computeLevels } from "./utils/layout";
|
import { layout, changeNode, computeLevels } from "./utils/layout";
|
||||||
import zoom from "@/views/dashboard/related/components/utils/zoom";
|
import zoom from "@/views/dashboard/related/components/utils/zoom";
|
||||||
import getDashboard from "@/hooks/useDashboardsSession";
|
|
||||||
|
|
||||||
/*global Nullable, defineProps */
|
/*global Nullable, defineProps */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -102,8 +99,7 @@ limitations under the License. -->
|
|||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip"]);
|
const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip", "getNodeMetrics"]);
|
||||||
const topologyStore = useTopologyStore();
|
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const height = ref<number>(100);
|
const height = ref<number>(100);
|
||||||
const width = ref<number>(100);
|
const width = ref<number>(100);
|
||||||
@ -124,37 +120,9 @@ limitations under the License. -->
|
|||||||
width.value = dom.width;
|
width.value = dom.width;
|
||||||
svg.value = d3.select(".hierarchy-services-svg");
|
svg.value = d3.select(".hierarchy-services-svg");
|
||||||
graph.value = d3.select(".hierarchy-services-graph");
|
graph.value = d3.select(".hierarchy-services-graph");
|
||||||
await update();
|
emits("getNodeMetrics");
|
||||||
svg.value.call(zoom(d3, graph.value, diff.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function update() {
|
|
||||||
const layerList = [];
|
|
||||||
const layerMap = new Map();
|
|
||||||
for (const n of props.nodes) {
|
|
||||||
if (layerMap.get(n.layer)) {
|
|
||||||
const arr = layerMap.get(n.layer);
|
|
||||||
arr.push(n);
|
|
||||||
layerMap.set(n.layer, arr);
|
|
||||||
} else {
|
|
||||||
layerMap.set(n.layer, [n]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const d of layerMap.values()) {
|
|
||||||
layerList.push(d);
|
|
||||||
}
|
|
||||||
for (const list of layerList) {
|
|
||||||
const { dashboard } = getDashboard(
|
|
||||||
{
|
|
||||||
layer: list[0].layer || "",
|
|
||||||
entity: EntityType[0].value,
|
|
||||||
},
|
|
||||||
ConfigFieldTypes.ISDEFAULT,
|
|
||||||
);
|
|
||||||
const exp = (dashboard && dashboard.expressions) || [];
|
|
||||||
await topologyStore.queryHierarchyNodeExpressions(exp, list[0].layer);
|
|
||||||
}
|
|
||||||
draw();
|
draw();
|
||||||
|
svg.value.call(zoom(d3, graph.value, diff.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
|
@ -20,10 +20,10 @@ limitations under the License. -->
|
|||||||
:style="`height: ${height}px`"
|
:style="`height: ${height}px`"
|
||||||
>
|
>
|
||||||
<Graph
|
<Graph
|
||||||
:config="config"
|
:nodes="topologyStore.hierarchyInstanceNodes"
|
||||||
:nodes="topologyStore.hierarchyServiceNodes"
|
:calls="topologyStore.hierarchyInstanceCalls"
|
||||||
:calls="topologyStore.hierarchyServiceCalls"
|
:entity="EntityType[3].value"
|
||||||
:entity="EntityType[0].value"
|
@getNodeMetrics="getNodeMetrics"
|
||||||
@showNodeTip="showNodeTip"
|
@showNodeTip="showNodeTip"
|
||||||
@handleNodeClick="handleNodeClick"
|
@handleNodeClick="handleNodeClick"
|
||||||
@hideTip="hideTip"
|
@hideTip="hideTip"
|
||||||
@ -32,7 +32,6 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { PropType } from "vue";
|
|
||||||
import { ref, onMounted, nextTick } from "vue";
|
import { ref, onMounted, nextTick } from "vue";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import type { Node } from "@/types/topology";
|
import type { Node } from "@/types/topology";
|
||||||
@ -46,13 +45,7 @@ limitations under the License. -->
|
|||||||
import getDashboard from "@/hooks/useDashboardsSession";
|
import getDashboard from "@/hooks/useDashboardsSession";
|
||||||
import Graph from "../components/Graph.vue";
|
import Graph from "../components/Graph.vue";
|
||||||
|
|
||||||
/*global Nullable, defineProps */
|
/*global Nullable*/
|
||||||
defineProps({
|
|
||||||
config: {
|
|
||||||
type: Object as PropType<any>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const topologyStore = useTopologyStore();
|
const topologyStore = useTopologyStore();
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const height = ref<number>(100);
|
const height = ref<number>(100);
|
||||||
@ -77,7 +70,7 @@ limitations under the License. -->
|
|||||||
|
|
||||||
async function getTopology() {
|
async function getTopology() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const resp = await topologyStore.getHierarchyServiceTopology();
|
const resp = await topologyStore.getHierarchyInstanceTopology();
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|
||||||
if (resp && resp.errors) {
|
if (resp && resp.errors) {
|
||||||
@ -85,6 +78,34 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getNodeMetrics() {
|
||||||
|
const layerList = [];
|
||||||
|
const layerMap = new Map();
|
||||||
|
for (const n of topologyStore.hierarchyInstanceNodes) {
|
||||||
|
if (layerMap.get(n.layer)) {
|
||||||
|
const arr = layerMap.get(n.layer);
|
||||||
|
arr.push(n);
|
||||||
|
layerMap.set(n.layer, arr);
|
||||||
|
} else {
|
||||||
|
layerMap.set(n.layer, [n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const d of layerMap.values()) {
|
||||||
|
layerList.push(d);
|
||||||
|
}
|
||||||
|
for (const list of layerList) {
|
||||||
|
const { dashboard } = getDashboard(
|
||||||
|
{
|
||||||
|
layer: list[0].layer || "",
|
||||||
|
entity: EntityType[3].value,
|
||||||
|
},
|
||||||
|
ConfigFieldTypes.ISDEFAULT,
|
||||||
|
);
|
||||||
|
const exp = (dashboard && dashboard.expressions) || [];
|
||||||
|
await topologyStore.queryHierarchyInstanceNodeExpressions(exp, list[0].layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showNodeTip(event: MouseEvent, data: Node) {
|
function showNodeTip(event: MouseEvent, data: Node) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
@ -96,7 +117,7 @@ limitations under the License. -->
|
|||||||
getDashboard(
|
getDashboard(
|
||||||
{
|
{
|
||||||
layer: data.layer || "",
|
layer: data.layer || "",
|
||||||
entity: EntityType[0].value,
|
entity: EntityType[3].value,
|
||||||
},
|
},
|
||||||
ConfigFieldTypes.ISDEFAULT,
|
ConfigFieldTypes.ISDEFAULT,
|
||||||
).dashboard || {};
|
).dashboard || {};
|
||||||
@ -104,7 +125,7 @@ limitations under the License. -->
|
|||||||
const nodeMetricConfig = dashboard.expressionsConfig || [];
|
const nodeMetricConfig = dashboard.expressionsConfig || [];
|
||||||
const html = exprssions.map((m: string, index: number) => {
|
const html = exprssions.map((m: string, index: number) => {
|
||||||
const metric =
|
const metric =
|
||||||
topologyStore.hierarchyNodeMetrics[data.layer || ""][m].values.find(
|
topologyStore.hierarchyInstanceNodeMetrics[data.layer || ""][m].values.find(
|
||||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||||
) || {};
|
) || {};
|
||||||
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
|
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
|
||||||
@ -135,7 +156,7 @@ limitations under the License. -->
|
|||||||
getDashboard(
|
getDashboard(
|
||||||
{
|
{
|
||||||
layer: d.layer || "",
|
layer: d.layer || "",
|
||||||
entity: EntityType[0].value,
|
entity: EntityType[3].value,
|
||||||
},
|
},
|
||||||
ConfigFieldTypes.ISDEFAULT,
|
ConfigFieldTypes.ISDEFAULT,
|
||||||
).dashboard || {};
|
).dashboard || {};
|
||||||
|
@ -24,6 +24,7 @@ limitations under the License. -->
|
|||||||
:nodes="topologyStore.hierarchyServiceNodes"
|
:nodes="topologyStore.hierarchyServiceNodes"
|
||||||
:calls="topologyStore.hierarchyServiceCalls"
|
:calls="topologyStore.hierarchyServiceCalls"
|
||||||
:entity="EntityType[0].value"
|
:entity="EntityType[0].value"
|
||||||
|
@getNodeMetrics="getNodeMetrics"
|
||||||
@showNodeTip="showNodeTip"
|
@showNodeTip="showNodeTip"
|
||||||
@handleNodeClick="handleNodeClick"
|
@handleNodeClick="handleNodeClick"
|
||||||
@hideTip="hideTip"
|
@hideTip="hideTip"
|
||||||
@ -85,6 +86,34 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getNodeMetrics() {
|
||||||
|
const layerList = [];
|
||||||
|
const layerMap = new Map();
|
||||||
|
for (const n of topologyStore.hierarchyServiceNodes) {
|
||||||
|
if (layerMap.get(n.layer)) {
|
||||||
|
const arr = layerMap.get(n.layer);
|
||||||
|
arr.push(n);
|
||||||
|
layerMap.set(n.layer, arr);
|
||||||
|
} else {
|
||||||
|
layerMap.set(n.layer, [n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const d of layerMap.values()) {
|
||||||
|
layerList.push(d);
|
||||||
|
}
|
||||||
|
for (const list of layerList) {
|
||||||
|
const { dashboard } = getDashboard(
|
||||||
|
{
|
||||||
|
layer: list[0].layer || "",
|
||||||
|
entity: EntityType[0].value,
|
||||||
|
},
|
||||||
|
ConfigFieldTypes.ISDEFAULT,
|
||||||
|
);
|
||||||
|
const exp = (dashboard && dashboard.expressions) || [];
|
||||||
|
await topologyStore.queryHierarchyNodeExpressions(exp, list[0].layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showNodeTip(event: MouseEvent, data: Node) {
|
function showNodeTip(event: MouseEvent, data: Node) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user