feat: implement Topology on the dashboard (#14)

This commit is contained in:
Fine0830
2022-02-19 23:05:57 +08:00
committed by GitHub
parent 7472d70720
commit f53b422782
81 changed files with 4886 additions and 232 deletions

View File

@@ -101,21 +101,86 @@ export const ConfigData2: any = {
},
children: [],
};
export const ConfigData3: any = {
export const ConfigData3: any = [
{
x: 0,
y: 0,
w: 8,
h: 12,
i: "0",
metrics: ["all_heatmap"],
metricTypes: ["readHeatMap"],
type: "Widget",
widget: {
title: "all_heatmap",
tips: "Tooltip",
},
graph: {
type: "HeatMap",
},
standard: {
unit: "min",
},
children: [],
},
];
export const ConfigData4: any = {
x: 0,
y: 0,
w: 8,
h: 12,
i: "0",
metrics: ["all_heatmap"],
metricTypes: ["readHeatMap"],
metrics: ["service_relation_server_resp_time"],
metricTypes: ["readMetricsValues"],
type: "Widget",
widget: {
title: "all_heatmap",
title: "service_relation_server_resp_time",
tips: "Tooltip",
},
graph: {
type: "HeatMap",
type: "Line",
},
standard: {
unit: "min",
},
children: [],
};
export const ConfigData5: any = {
x: 0,
y: 0,
w: 8,
h: 12,
i: "0",
metrics: ["endpoint_relation_cpm"],
metricTypes: ["readMetricsValues"],
type: "Widget",
widget: {
title: "endpoint_relation_cpm",
tips: "Tooltip",
},
graph: {
type: "Line",
},
standard: {
unit: "min",
},
children: [],
};
export const ConfigData6: any = {
x: 0,
y: 0,
w: 8,
h: 12,
i: "0",
metrics: ["service_instance_relation_server_cpm"],
metricTypes: ["readMetricsValues"],
type: "Widget",
widget: {
title: "service_instance_relation_server_cpm",
tips: "Tooltip",
},
graph: {
type: "Line",
},
standard: {
unit: "min",

View File

@@ -17,14 +17,22 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import { LayoutConfig } from "@/types/dashboard";
import graph from "@/graph";
import { ConfigData, ConfigData1, ConfigData2, ConfigData3 } from "../data";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import {
ConfigData,
ConfigData1,
ConfigData2,
ConfigData3,
ConfigData4,
ConfigData5,
ConfigData6,
} from "../data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl } from "../data";
import { Duration } from "@/types/app";
import axios, { AxiosResponse } from "axios";
import { cancelToken } from "@/utils/cancelToken";
import { AxiosResponse } from "axios";
interface DashboardState {
showConfig: boolean;
layout: LayoutConfig[];
@@ -34,6 +42,7 @@ interface DashboardState {
activedGridItem: string;
durationTime: Duration;
selectorStore: any;
showTopology: boolean;
}
export const dashboardStore = defineStore({
@@ -47,6 +56,7 @@ export const dashboardStore = defineStore({
activedGridItem: "",
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(),
showTopology: false,
}),
actions: {
setLayout(data: LayoutConfig[]) {
@@ -73,6 +83,17 @@ export const dashboardStore = defineStore({
},
];
}
if (type === "Topology") {
newWidget.w = 4;
newWidget.h = 6;
newWidget.graph = {
fontColor: "white",
backgroundColor: "green",
iconTheme: true,
content: "Topology",
fontSize: 18,
};
}
this.layout = this.layout.map((d: LayoutConfig) => {
d.y = d.y + newWidget.h;
return d;
@@ -154,11 +175,23 @@ export const dashboardStore = defineStore({
this.layout = [ConfigData2];
}
if (type == "All") {
this.layout = [ConfigData3];
this.layout = ConfigData3;
}
if (type == "Service") {
this.layout = [ConfigData];
}
if (type == "ServiceRelation") {
this.layout = [ConfigData4];
}
if (type == "ServiceInstanceRelation") {
this.layout = [ConfigData6];
}
if (type == "EndpointRelation") {
this.layout = [ConfigData5];
}
},
setTopology(show: boolean) {
this.showTopology = show;
},
setConfigs(param: { [key: string]: unknown }) {
const actived = this.activedGridItem.split("-");
@@ -181,14 +214,14 @@ export const dashboardStore = defineStore({
this.selectedGrid = this.layout[index];
},
async fetchMetricType(item: string) {
const res: AxiosResponse = await graph
const res: AxiosResponse = await graphql
.query("queryTypeOfMetrics")
.params({ name: item });
return res.data;
},
async fetchMetricList(regex: string) {
const res: AxiosResponse = await graph
const res: AxiosResponse = await graphql
.query("queryMetrics")
.params({ regex });
@@ -198,11 +231,7 @@ export const dashboardStore = defineStore({
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await axios.post(
"/graphql",
{ query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() }
);
const res: AxiosResponse = await query(param);
return res.data;
},
},

View File

@@ -18,17 +18,19 @@ import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Service, Instance, Endpoint } from "@/types/selector";
import { store } from "@/store";
import graph from "@/graph";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface SelectorState {
services: Service[];
destServices: Service[];
pods: Array<Instance | Endpoint>;
currentService: Nullable<Service>;
currentPod: Nullable<Instance | Endpoint>;
currentDestService: Nullable<Service>;
currentDestPod: Nullable<Instance | Endpoint>;
destPods: Array<Instance | Endpoint>;
durationTime: Duration;
}
@@ -36,7 +38,9 @@ export const selectorStore = defineStore({
id: "selector",
state: (): SelectorState => ({
services: [],
destServices: [],
pods: [],
destPods: [],
currentService: null,
currentPod: null,
currentDestService: null,
@@ -44,103 +48,134 @@ export const selectorStore = defineStore({
durationTime: useAppStoreWithOut().durationTime,
}),
actions: {
setCurrentService(service: Service) {
setCurrentService(service: Nullable<Service>) {
this.currentService = service;
},
setCurrentDestService(service: Nullable<Service>) {
this.currentDestService = service;
},
setCurrentPod(pod: Nullable<Instance | Endpoint>) {
this.currentPod = pod;
},
setCurrentDestPod(pod: Nullable<Instance | Endpoint>) {
this.currentDestPod = pod;
},
async fetchLayers(): Promise<AxiosResponse> {
const res: AxiosResponse = await graph.query("queryLayers").params({});
const res: AxiosResponse = await graphql.query("queryLayers").params({});
return res.data || {};
},
async fetchServices(layer: string): Promise<AxiosResponse> {
const res: AxiosResponse = await graph
const res: AxiosResponse = await graphql
.query("queryServices")
.params({ layer });
if (!res.data.errors) {
this.services = res.data.data.services || [];
this.destServices = res.data.data.services || [];
}
return res.data;
},
async getServiceInstances(param?: {
serviceId: string;
isRelation: boolean;
}): Promise<Nullable<AxiosResponse>> {
const serviceId = param ? param.serviceId : this.currentService?.id;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graph.query("queryInstances").params({
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: this.durationTime,
});
if (!res.data.errors) {
if (param && param.isRelation) {
this.destPods = res.data.data.pods || [];
return res.data;
}
this.pods = res.data.data.pods || [];
}
return res.data;
},
async getEndpoints(params?: {
async getEndpoints(params: {
keyword?: string;
serviceId?: string;
isRelation?: boolean;
}): Promise<Nullable<AxiosResponse>> {
if (!params) {
params = {};
}
if (!params.keyword) {
params.keyword = "";
}
const serviceId = params.serviceId || this.currentService?.id;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graph.query("queryEndpoints").params({
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: params.keyword,
keyword: params.keyword || "",
});
if (!res.data.errors) {
if (params.isRelation) {
this.destPods = res.data.data.pods || [];
return res.data;
}
this.pods = res.data.data.pods || [];
}
return res.data;
},
async getService(serviceId: string) {
async getService(serviceId: string, isRelation: boolean) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graph.query("queryService").params({
const res: AxiosResponse = await graphql.query("queryService").params({
serviceId,
});
if (!res.data.errors) {
this.currentService = res.data.data.service || {};
if (isRelation) {
this.setCurrentDestService(res.data.data.service);
this.destServices = [res.data.data.service];
return res.data;
}
this.setCurrentService(res.data.data.service);
this.services = [res.data.data.service];
}
return res.data;
},
async getInstance(instanceId: string) {
async getInstance(instanceId: string, isRelation?: boolean) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graph.query("queryInstance").params({
const res: AxiosResponse = await graphql.query("queryInstance").params({
instanceId,
});
if (!res.data.errors) {
if (isRelation) {
this.currentDestPod = res.data.data.instance || null;
this.destPods = [res.data.data.instance];
return;
}
this.currentPod = res.data.data.instance || null;
this.pods = [res.data.data.instance];
}
return res.data;
},
async getEndpoint(endpointId: string) {
async getEndpoint(endpointId: string, isRelation?: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graph.query("queryEndpoint").params({
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
endpointId,
});
if (!res.data.errors) {
if (isRelation) {
this.currentDestPod = res.data.data.endpoint || null;
this.destPods = [res.data.data.endpoint];
return;
}
this.currentPod = res.data.data.endpoint || null;
this.pods = [res.data.data.endpoint];
}
return res.data;

View File

@@ -0,0 +1,460 @@
/**
* 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 { store } from "@/store";
import { Service } from "@/types/selector";
import { Node, Call } from "@/types/topology";
import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { AxiosResponse } from "axios";
import query from "@/graphql/fetch";
interface MetricVal {
[key: string]: { values: { id: string; value: unknown }[] };
}
interface TopologyState {
node: Nullable<Node>;
call: Nullable<Call>;
calls: Call[];
nodes: Node[];
nodeMetrics: MetricVal;
linkServerMetrics: MetricVal;
linkClientMetrics: MetricVal;
defaultDepth: string;
}
export const topologyStore = defineStore({
id: "topology",
state: (): TopologyState => ({
calls: [],
nodes: [],
node: null,
call: null,
nodeMetrics: {},
linkServerMetrics: {},
linkClientMetrics: {},
defaultDepth: "2",
}),
actions: {
setNode(node: Node) {
this.node = node;
},
setLink(link: Call) {
this.call = link;
},
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;
},
setTopology(data: { nodes: Node[]; calls: Call[] }) {
const obj = {} as any;
const services = useSelectorStore().services;
const nodes = data.nodes.reduce((prev: Node[], next: Node) => {
if (!obj[next.id]) {
obj[next.id] = true;
const s = services.filter((d: Service) => d.id === next.id)[0] || {};
next.layer = s.layers ? s.layers[0] : null;
prev.push(next);
}
return prev;
}, []);
const calls = data.calls.reduce((prev: Call[], next: Call) => {
if (!obj[next.id]) {
obj[next.id] = true;
next.value = next.value || 1;
for (const node of data.nodes) {
if (next.source === node.id) {
next.sourceObj = node;
}
if (next.target === node.id) {
next.targetObj = node;
}
}
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;
},
setLinkServerMetrics(m: { id: string; value: unknown }[]) {
this.linkServerMetrics = m;
},
setLinkClientMetrics(m: { id: string; value: unknown }[]) {
this.linkClientMetrics = m;
},
setDefaultDepth(val: number) {
this.defaultDepth = val;
},
async getDepthServiceTopology(serviceIds: string[], depth: number) {
const res = await this.getServicesTopology(serviceIds);
if (depth > 1) {
const ids = res.nodes
.map((item: Node) => item.id)
.filter((d: string) => !serviceIds.includes(d));
if (!ids.length) {
this.setTopology(res);
return;
}
const json = await this.getServicesTopology(ids);
if (depth > 2) {
const pods = json.nodes
.map((item: Node) => item.id)
.filter((d: string) => ![...ids, ...serviceIds].includes(d));
if (!pods.length) {
const nodes = [...res.nodes, ...json.nodes];
const calls = [...res.calls, ...json.calls];
this.setTopology({ nodes, calls });
return;
}
const topo = await this.getServicesTopology(pods);
if (depth > 3) {
const services = topo.nodes
.map((item: Node) => item.id)
.filter(
(d: string) => ![...ids, ...pods, ...serviceIds].includes(d)
);
if (!services.length) {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls];
this.setTopology({ nodes, calls });
return;
}
const data = await this.getServicesTopology(services);
if (depth > 4) {
const nodeIds = data.nodes
.map((item: Node) => item.id)
.filter(
(d: string) =>
![...services, ...ids, ...pods, ...serviceIds].includes(d)
);
if (!nodeIds.length) {
const nodes = [
...res.nodes,
...json.nodes,
...topo.nodes,
...data.nodes,
];
const calls = [
...res.calls,
...json.calls,
...topo.calls,
...data.calls,
];
this.setTopology({ nodes, calls });
return;
}
const toposObj = await this.getServicesTopology(nodeIds);
const nodes = [
...res.nodes,
...json.nodes,
...topo.nodes,
...data.nodes,
...toposObj.nodes,
];
const calls = [
...res.calls,
...json.calls,
...topo.calls,
...data.calls,
...toposObj.calls,
];
this.setTopology({ nodes, calls });
} else {
const nodes = [
...res.nodes,
...json.nodes,
...topo.nodes,
...data.nodes,
];
const calls = [
...res.calls,
...json.calls,
...topo.calls,
...data.calls,
];
this.setTopology({ nodes, calls });
}
} else {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls];
this.setTopology({ nodes, calls });
}
} else {
this.setTopology({
nodes: [...res.nodes, ...json.nodes],
calls: [...res.calls, ...json.calls],
});
}
} else {
this.setTopology(res);
}
},
async getServicesTopology(serviceIds: string[]) {
const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql
.query("getServicesTopology")
.params({
serviceIds,
duration,
});
if (res.data.errors) {
return res.data;
}
return res.data.data.topology;
},
async getInstanceTopology() {
const serverServiceId = useSelectorStore().currentService.id;
const clientServiceId = useSelectorStore().currentDestService.id;
const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql
.query("getInstanceTopology")
.params({
clientServiceId,
serverServiceId,
duration,
});
if (!res.data.errors) {
this.setInstanceTopology(res.data.data.topology);
}
return res.data;
},
async updateEndpointTopology(endpointIds: string[], depth: number) {
const res = await this.getEndpointTopology(endpointIds);
if (depth > 1) {
const ids = res.nodes
.map((item: Node) => item.id)
.filter((d: string) => !endpointIds.includes(d));
if (!ids.length) {
this.setTopology(res);
return;
}
const json = await this.getEndpointTopology(ids);
if (depth > 2) {
const pods = json.nodes
.map((item: Node) => item.id)
.filter((d: string) => ![...ids, ...endpointIds].includes(d));
if (!pods.length) {
const nodes = [...res.nodes, ...json.nodes];
const calls = [...res.calls, ...json.calls];
this.setTopology({ nodes, calls });
return;
}
const topo = await this.getEndpointTopology(pods);
if (depth > 3) {
const endpoints = topo.nodes
.map((item: Node) => item.id)
.filter(
(d: string) => ![...ids, ...pods, ...endpointIds].includes(d)
);
if (!endpoints.length) {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls];
this.setTopology({ nodes, calls });
return;
}
const data = await this.getEndpointTopology(endpoints);
if (depth > 4) {
const nodeIds = data.nodes
.map((item: Node) => item.id)
.filter(
(d: string) =>
![...endpoints, ...ids, ...pods, ...endpointIds].includes(d)
);
if (!nodeIds.length) {
const nodes = [
...res.nodes,
...json.nodes,
...topo.nodes,
...data.nodes,
];
const calls = [
...res.calls,
...json.calls,
...topo.calls,
...data.calls,
];
this.setTopology({ nodes, calls });
return;
}
const toposObj = await this.getEndpointTopology(nodeIds);
const nodes = [
...res.nodes,
...json.nodes,
...topo.nodes,
...data.nodes,
...toposObj.nodes,
];
const calls = [
...res.calls,
...json.calls,
...topo.calls,
...data.calls,
...toposObj.calls,
];
this.setTopology({ nodes, calls });
} else {
const nodes = [
...res.nodes,
...json.nodes,
...topo.nodes,
...data.nodes,
];
const calls = [
...res.calls,
...json.calls,
...topo.calls,
...data.calls,
];
this.setTopology({ nodes, calls });
}
} else {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls];
this.setTopology({ nodes, calls });
}
} else {
this.setTopology({
nodes: [...res.nodes, ...json.nodes],
calls: [...res.calls, ...json.calls],
});
}
} else {
this.setTopology(res);
}
},
async getEndpointTopology(endpointIds: string[]) {
const duration = useAppStoreWithOut().durationTime;
const variables = ["$duration: Duration!"];
const fragment = endpointIds.map((id: string, index: number) => {
return `endpointTopology${index}: getEndpointDependencies(endpointId: "${id}", duration: $duration) {
nodes {
id
name
serviceId
serviceName
type
isReal
}
calls {
id
source
target
detectPoints
}
}`;
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
const conditions = { duration };
const res: AxiosResponse = await query({ queryStr, conditions });
if (res.data.errors) {
return res.data;
}
const topo = res.data.data;
const calls = [] as any;
const nodes = [] as any;
for (const key of Object.keys(topo)) {
calls.push(...topo[key].calls);
nodes.push(...topo[key].nodes);
}
// this.setTopology({ calls, nodes });
return { calls, nodes };
},
async getNodeMetrics(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
this.setNodeMetrics(res.data.data);
return res.data;
},
async getLegendMetrics(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
const data = res.data.data;
const metrics = Object.keys(data);
this.nodes = this.nodes.map((d: Node | any) => {
for (const m of metrics) {
for (const val of data[m].values) {
if (d.id === val.id) {
d[m] = val.value;
}
}
}
return d;
});
return res.data;
},
async getCallServerMetrics(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
this.setLinkServerMetrics(res.data.data);
return res.data;
},
async getCallClientMetrics(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
this.setLinkClientMetrics(res.data.data);
return res.data;
},
},
});
export function useTopologyStore(): any {
return topologyStore(store);
}