mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-04-30 23:04:00 +00:00
feat: implement MQE
on topology widget (#312)
This commit is contained in:
parent
8c1ddb109c
commit
60a4232759
@ -44,6 +44,7 @@ module.exports = {
|
||||
"workflow",
|
||||
"types",
|
||||
"release",
|
||||
"merge",
|
||||
],
|
||||
],
|
||||
},
|
||||
|
87
src/components/Tags.vue
Normal file
87
src/components/Tags.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<!-- 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>
|
||||
<span :class="vertical ? 'vertical' : 'horizontal'" v-for="tag in dynamicTags" :key="tag">
|
||||
<el-tag closable :disable-transitions="false" @close="handleClose(tag)">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<el-input
|
||||
v-if="inputVisible"
|
||||
ref="InputRef"
|
||||
v-model="inputValue"
|
||||
class="ml-5 input-name"
|
||||
size="small"
|
||||
@keyup.enter="handleInputConfirm"
|
||||
@blur="handleInputConfirm"
|
||||
/>
|
||||
<el-button v-else size="small" @click="showInput"> + {{ text }} </el-button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { ElInput } from "element-plus";
|
||||
|
||||
/*global defineProps, defineEmits*/
|
||||
const emits = defineEmits(["change"]);
|
||||
const props = defineProps({
|
||||
tags: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
text: { type: String, default: "" },
|
||||
vertical: { type: Boolean, default: false },
|
||||
});
|
||||
const inputValue = ref("");
|
||||
const dynamicTags = ref<string[]>(props.tags || []);
|
||||
const inputVisible = ref(false);
|
||||
const InputRef = ref<InstanceType<typeof ElInput>>();
|
||||
|
||||
const handleClose = (tag: string) => {
|
||||
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1);
|
||||
};
|
||||
|
||||
const showInput = () => {
|
||||
inputVisible.value = true;
|
||||
nextTick(() => {
|
||||
InputRef.value!.input!.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue.value) {
|
||||
dynamicTags.value.push(inputValue.value);
|
||||
}
|
||||
inputVisible.value = false;
|
||||
inputValue.value = "";
|
||||
emits("change", dynamicTags.value);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.input-name {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
@ -21,6 +21,7 @@ import Selector from "./Selector.vue";
|
||||
import Graph from "./Graph.vue";
|
||||
import Radio from "./Radio.vue";
|
||||
import SelectSingle from "./SelectSingle.vue";
|
||||
import Tags from "./Tags.vue";
|
||||
import VueGridLayout from "vue-grid-layout";
|
||||
|
||||
const components: Indexable = {
|
||||
@ -31,6 +32,7 @@ const components: Indexable = {
|
||||
Graph,
|
||||
Radio,
|
||||
SelectSingle,
|
||||
Tags,
|
||||
};
|
||||
const componentsName: string[] = Object.keys(components);
|
||||
|
||||
|
@ -15,13 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RespFields } from "./data";
|
||||
import { ExpressionResultType } from "@/views/dashboard/data";
|
||||
import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import type { Instance, Endpoint, Service } from "@/types/selector";
|
||||
import type { Node, Call } from "@/types/topology";
|
||||
|
||||
export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
function expressionsGraphqlPods() {
|
||||
@ -312,3 +313,88 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
|
||||
return expressionParams;
|
||||
}
|
||||
|
||||
export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) {
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
function getExpressionQuery() {
|
||||
const conditions: { [key: string]: unknown } = {
|
||||
duration: appStore.durationTime,
|
||||
};
|
||||
const variables: string[] = [`$duration: Duration!`];
|
||||
const fragmentList = instances.map((d: any, index: number) => {
|
||||
let serviceName;
|
||||
let destServiceName;
|
||||
let endpointName;
|
||||
let serviceInstanceName;
|
||||
let destServiceInstanceName;
|
||||
let destEndpointName;
|
||||
if (d.sourceObj && d.targetObj) {
|
||||
// instances = Calls
|
||||
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
|
||||
destServiceName = d.targetObj.serviceName || d.targetObj.name;
|
||||
if (EntityType[4].value === dashboardStore.entity) {
|
||||
serviceInstanceName = d.sourceObj.name;
|
||||
destServiceInstanceName = d.targetObj.name;
|
||||
}
|
||||
if (EntityType[2].value === dashboardStore.entity) {
|
||||
endpointName = d.sourceObj.name;
|
||||
destEndpointName = d.targetObj.name;
|
||||
}
|
||||
} else {
|
||||
// instances = Nodes
|
||||
serviceName = d.serviceName || d.name;
|
||||
if (EntityType[4].value === dashboardStore.entity) {
|
||||
serviceInstanceName = d.name;
|
||||
}
|
||||
if (EntityType[2].value === dashboardStore.entity) {
|
||||
endpointName = d.name;
|
||||
}
|
||||
}
|
||||
const entity = {
|
||||
serviceName,
|
||||
normal: true,
|
||||
serviceInstanceName,
|
||||
endpointName,
|
||||
destServiceName,
|
||||
destNormal: destServiceName ? true : undefined,
|
||||
destServiceInstanceName,
|
||||
destEndpointName,
|
||||
};
|
||||
variables.push(`$entity${index}: Entity!`);
|
||||
conditions[`entity${index}`] = entity;
|
||||
const f = metrics.map((name: string, idx: number) => {
|
||||
if (index === 0) {
|
||||
variables.push(`$expression${idx}: String!`);
|
||||
conditions[`expression${idx}`] = name;
|
||||
}
|
||||
return `expression${index}${idx}: execExpression(expression: $expression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
});
|
||||
return f;
|
||||
});
|
||||
const fragment = fragmentList.flat(1).join(" ");
|
||||
const queryStr = `query queryData(${variables}) {${fragment}}`;
|
||||
|
||||
return { queryStr, conditions };
|
||||
}
|
||||
function handleExpressionValues(resp: { [key: string]: any }) {
|
||||
const obj: any = {};
|
||||
for (let idx = 0; idx < instances.length; idx++) {
|
||||
for (let index = 0; index < metrics.length; index++) {
|
||||
const k = "expression" + idx + index;
|
||||
if (metrics[index]) {
|
||||
if (!obj[metrics[index]]) {
|
||||
obj[metrics[index]] = {
|
||||
values: [],
|
||||
};
|
||||
}
|
||||
obj[metrics[index]].values.push({ value: resp[k].results[0].values[0].value, id: instances[idx].id });
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
return { getExpressionQuery, handleExpressionValues };
|
||||
}
|
||||
|
@ -378,5 +378,9 @@ const msg = {
|
||||
menus: "Menus",
|
||||
saveReload: "Save and reload the page",
|
||||
document: "Documentation",
|
||||
metricMode: "Metric Mode",
|
||||
addExpressions: "Add Expressions",
|
||||
expressions: "Expression",
|
||||
unhealthyExpression: "Unhealthy Expression",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -378,5 +378,9 @@ const msg = {
|
||||
menus: "Menus",
|
||||
saveReload: "Save and reload the page",
|
||||
document: "Documentation",
|
||||
metricMode: "Metric Mode",
|
||||
addExpressions: "Add Expressions",
|
||||
expressions: "Expression",
|
||||
unhealthyExpression: "Unhealthy Expression",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -376,5 +376,9 @@ const msg = {
|
||||
menusManagement: "菜单",
|
||||
saveReload: "保存并重新加载页面",
|
||||
document: "文档",
|
||||
metricMode: "指标模式",
|
||||
addExpressions: "添加表达式",
|
||||
expressions: "表达式",
|
||||
unhealthyExpression: "非健康表达式",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -57,7 +57,7 @@ export const eventStore = defineStore({
|
||||
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
|
||||
return res.data;
|
||||
},
|
||||
async getEndpoints(keyword?: string) {
|
||||
async getEndpoints(keyword: string) {
|
||||
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
|
||||
if (!serviceId) {
|
||||
return;
|
||||
|
@ -24,6 +24,7 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import query from "@/graphql/fetch";
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
interface MetricVal {
|
||||
@ -114,6 +115,16 @@ export const topologyStore = defineStore({
|
||||
setLinkClientMetrics(m: MetricVal) {
|
||||
this.linkClientMetrics = m;
|
||||
},
|
||||
setLegendValues(expressions: string, data: { [key: string]: any }) {
|
||||
for (let idx = 0; idx < this.nodes.length; idx++) {
|
||||
for (let index = 0; index < expressions.length; index++) {
|
||||
const k = "expression" + idx + index;
|
||||
if (expressions[index]) {
|
||||
this.nodes[idx][expressions[index]] = Number(data[k].results[0].values[0].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async getDepthServiceTopology(serviceIds: string[], depth: number) {
|
||||
const res = await this.getServicesTopology(serviceIds);
|
||||
if (depth > 1) {
|
||||
@ -321,6 +332,15 @@ export const topologyStore = defineStore({
|
||||
this.setNodeMetricValue(res.data.data);
|
||||
return res.data;
|
||||
},
|
||||
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
||||
const res: AxiosResponse = await query(param);
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getLinkClientMetrics(linkClientMetrics: string[]) {
|
||||
if (!linkClientMetrics.length) {
|
||||
this.setLinkClientMetrics({});
|
||||
@ -353,6 +373,29 @@ export const topologyStore = defineStore({
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
},
|
||||
async getLinkExpressions(expressions: string[], type: string) {
|
||||
if (!expressions.length) {
|
||||
this.setLinkServerMetrics({});
|
||||
return;
|
||||
}
|
||||
const calls = this.calls.filter((i: Call) => i.detectPoints.includes(type));
|
||||
if (!calls.length) {
|
||||
return;
|
||||
}
|
||||
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, calls);
|
||||
const param = getExpressionQuery();
|
||||
const res = await this.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
const metrics = handleExpressionValues(res.data);
|
||||
if (type === "SERVER") {
|
||||
this.setLinkServerMetrics(metrics);
|
||||
} else {
|
||||
this.setLinkClientMetrics(metrics);
|
||||
}
|
||||
},
|
||||
async queryNodeMetrics(nodeMetrics: string[]) {
|
||||
if (!nodeMetrics.length) {
|
||||
this.setNodeMetricValue({});
|
||||
@ -369,6 +412,28 @@ export const topologyStore = defineStore({
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
},
|
||||
async queryNodeExpressions(expressions: string[]) {
|
||||
if (!expressions.length) {
|
||||
this.setNodeMetricValue({});
|
||||
return;
|
||||
}
|
||||
if (!this.nodes.length) {
|
||||
this.setNodeMetricValue({});
|
||||
return;
|
||||
}
|
||||
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(
|
||||
expressions,
|
||||
this.nodes,
|
||||
);
|
||||
const param = getExpressionQuery();
|
||||
const res = await this.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
const metrics = handleExpressionValues(res.data);
|
||||
this.setNodeMetricValue(metrics);
|
||||
},
|
||||
async getLegendMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
||||
const res: AxiosResponse = await query(param);
|
||||
|
||||
|
2
src/types/components.d.ts
vendored
2
src/types/components.d.ts
vendored
@ -36,6 +36,7 @@ declare module '@vue/runtime-core' {
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
Graph: typeof import('./../components/Graph.vue')['default']
|
||||
Icon: typeof import('./../components/Icon.vue')['default']
|
||||
@ -45,6 +46,7 @@ declare module '@vue/runtime-core' {
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Selector: typeof import('./../components/Selector.vue')['default']
|
||||
SelectSingle: typeof import('./../components/SelectSingle.vue')['default']
|
||||
Tags: typeof import('./../components/Tags.vue')['default']
|
||||
TimePicker: typeof import('./../components/TimePicker.vue')['default']
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ limitations under the License. -->
|
||||
},
|
||||
});
|
||||
const dashboardStore = useDashboardStore();
|
||||
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression ? true : false);
|
||||
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
|
||||
const metrics = computed(
|
||||
() => (isExpression.value ? dashboardStore.selectedGrid.expressions : dashboardStore.selectedGrid.metrics) || [],
|
||||
);
|
||||
|
@ -139,7 +139,7 @@ limitations under the License. -->
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { EntityType, DepthList } from "../../../data";
|
||||
import { EntityType, DepthList, MetricModes } from "../../../data";
|
||||
import router from "@/router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Settings from "./Settings.vue";
|
||||
@ -153,6 +153,7 @@ limitations under the License. -->
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||
import { layout, circleIntersection, computeCallPos } from "./utils/layout";
|
||||
import zoom from "../../components/utils/zoom";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
|
||||
/*global Nullable, defineProps */
|
||||
const props = defineProps({
|
||||
@ -220,19 +221,27 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
async function update() {
|
||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
|
||||
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || []);
|
||||
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || []);
|
||||
} else {
|
||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", resize);
|
||||
await initLegendMetrics();
|
||||
draw();
|
||||
tooltip.value = d3.select("#tooltip");
|
||||
setNodeTools(settings.value.nodeDashboard);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
const node = findMostFrequent(topologyStore.calls);
|
||||
const levels = [];
|
||||
const nodes = topologyStore.nodes.sort((a: Node, b: Node) => {
|
||||
const nodes = JSON.parse(JSON.stringify(topologyStore.nodes)).sort((a: Node, b: Node) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
@ -352,18 +361,49 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
async function initLegendMetrics() {
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
const names = props.config.legend.map((d: any) => d.name);
|
||||
if (names.length && ids.length) {
|
||||
const param = await useQueryTopologyMetrics(names, ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
if (!topologyStore.nodes.length) {
|
||||
return;
|
||||
}
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
const expression = props.config.legendMQE && props.config.legendMQE.expression;
|
||||
if (!expression) {
|
||||
return;
|
||||
}
|
||||
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
|
||||
const param = getExpressionQuery();
|
||||
const res = await topologyStore.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
} else {
|
||||
topologyStore.setLegendValues([expression], res.data);
|
||||
}
|
||||
} else {
|
||||
const names = props.config.legend.map((d: any) => d.name);
|
||||
if (!names.length) {
|
||||
return;
|
||||
}
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
if (ids.length) {
|
||||
const param = await useQueryTopologyMetrics(names, ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeStatus(d: any) {
|
||||
const legend = settings.value.legend;
|
||||
const { legend, legendMQE } = settings.value;
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
if (!legendMQE) {
|
||||
return icons.CUBE;
|
||||
}
|
||||
if (!legendMQE.expression) {
|
||||
return icons.CUBE;
|
||||
}
|
||||
return Number(d[legendMQE.expression]) && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
||||
}
|
||||
if (!legend) {
|
||||
return icons.CUBE;
|
||||
}
|
||||
@ -381,7 +421,10 @@ limitations under the License. -->
|
||||
return c && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
||||
}
|
||||
function showNodeTip(event: MouseEvent, data: Node) {
|
||||
const nodeMetrics: string[] = settings.value.nodeMetrics || [];
|
||||
const nodeMetrics: string[] =
|
||||
(settings.value.metricMode === MetricModes.Expression
|
||||
? settings.value.nodeExpressions
|
||||
: settings.value.nodeMetrics) || [];
|
||||
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
|
||||
const html = nodeMetrics.map((m, index) => {
|
||||
const metric =
|
||||
@ -404,10 +447,16 @@ limitations under the License. -->
|
||||
.html(tipHtml);
|
||||
}
|
||||
function showLinkTip(event: MouseEvent, data: Call) {
|
||||
const linkClientMetrics: string[] = settings.value.linkClientMetrics || [];
|
||||
const linkClientMetrics: string[] =
|
||||
settings.value.metricMode === MetricModes.Expression
|
||||
? settings.value.linkClientExpressions
|
||||
: settings.value.linkClientMetrics || [];
|
||||
const linkServerMetricConfig: MetricConfigOpt[] = settings.value.linkServerMetricConfig || [];
|
||||
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
|
||||
const linkServerMetrics: string[] = settings.value.linkServerMetrics || [];
|
||||
const linkServerMetrics: string[] =
|
||||
settings.value.metricMode === MetricModes.Expression
|
||||
? settings.value.linkServerExpressions
|
||||
: settings.value.linkServerMetrics || [];
|
||||
const htmlServer = linkServerMetrics.map((m, index) => {
|
||||
const metric = topologyStore.linkServerMetrics[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||
@ -667,7 +716,7 @@ limitations under the License. -->
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
color: $disabled-color;
|
||||
border: 1px solid $disabled-color;
|
||||
border: 1px solid #eee;
|
||||
background-color: $theme-background;
|
||||
box-shadow: #eee 1px 2px 10px;
|
||||
transition: all 0.5ms linear;
|
||||
|
@ -15,7 +15,9 @@ limitations under the License. -->
|
||||
<template>
|
||||
<div class="config-panel">
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("metrics") }}</span>
|
||||
<span class="label">{{
|
||||
t(dashboardStore.selectedGrid.metricMode === MetricModes.General ? "metrics" : "expressions")
|
||||
}}</span>
|
||||
<SelectSingle :value="currentMetric" :options="metrics" @change="changeMetric" class="selectors" />
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
@ -38,7 +40,7 @@ limitations under the License. -->
|
||||
@change="changeConfigs({ label: currentConfig.label })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<div class="item mb-10" v-if="dashboardStore.selectedGrid.metricMode === MetricModes.General">
|
||||
<span class="label">{{ t("aggregation") }}</span>
|
||||
<SelectSingle
|
||||
:value="currentConfig.calculation"
|
||||
@ -54,17 +56,12 @@ limitations under the License. -->
|
||||
import { ref, computed, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { CalculationOpts } from "../../../data";
|
||||
import { CalculationOpts, MetricModes } from "../../../data";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import type { Option } from "element-plus/es/components/select-v2/src/select.types";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
currentMetricConfig: {
|
||||
type: Object as PropType<MetricConfigOpt>,
|
||||
default: () => ({ unit: "" }),
|
||||
},
|
||||
type: { type: String, default: "" },
|
||||
metrics: { type: Array as PropType<string[]>, default: () => [] },
|
||||
});
|
||||
|
@ -22,6 +22,7 @@ limitations under the License. -->
|
||||
:options="DepthList"
|
||||
placeholder="Select a option"
|
||||
@change="changeDepth"
|
||||
size="small"
|
||||
/>
|
||||
</span>
|
||||
<span class="switch-icon ml-5" title="Settings" @click="setConfig" v-if="dashboardStore.editMode">
|
||||
@ -68,7 +69,7 @@ limitations under the License. -->
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { EntityType, DepthList } from "../../../data";
|
||||
import { EntityType, DepthList, MetricModes } from "../../../data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Sankey from "./Sankey.vue";
|
||||
import Settings from "./Settings.vue";
|
||||
@ -118,9 +119,15 @@ limitations under the License. -->
|
||||
};
|
||||
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 || []);
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
|
||||
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || []);
|
||||
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || []);
|
||||
} else {
|
||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||
}
|
||||
}
|
||||
|
||||
function resize() {
|
||||
@ -265,7 +272,6 @@ limitations under the License. -->
|
||||
<style lang="scss" scoped>
|
||||
.sankey {
|
||||
margin-top: 10px;
|
||||
background-color: #333840 !important;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
@ -275,7 +281,8 @@ limitations under the License. -->
|
||||
right: 10px;
|
||||
width: 400px;
|
||||
height: 600px;
|
||||
background-color: #2b3037;
|
||||
border: 1px solid #eee;
|
||||
background-color: $theme-background;
|
||||
overflow: auto;
|
||||
padding: 10px 15px;
|
||||
border-radius: 3px;
|
||||
@ -283,6 +290,7 @@ limitations under the License. -->
|
||||
transition: all 0.5ms linear;
|
||||
z-index: 99;
|
||||
text-align: left;
|
||||
box-shadow: #eee 1px 2px 10px;
|
||||
}
|
||||
|
||||
.tool {
|
||||
@ -299,8 +307,8 @@ limitations under the License. -->
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.5ms linear;
|
||||
background-color: #252a2f99;
|
||||
color: #ddd;
|
||||
background: rgb(0 0 0 / 30%);
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ limitations under the License. -->
|
||||
import type { Node, Call } from "@/types/topology";
|
||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import { aggregation } from "@/hooks/useMetricsProcessor";
|
||||
import { MetricModes } from "../../../data";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
@ -51,16 +52,16 @@ limitations under the License. -->
|
||||
data: topologyStore.nodes,
|
||||
links: topologyStore.calls,
|
||||
label: {
|
||||
color: "#fff",
|
||||
color: "#666",
|
||||
formatter: (param: any) => param.data.name,
|
||||
},
|
||||
color: ["#3fe1da", "#6be6c1", "#3fcfdc", "#626c91", "#3fbcde", "#a0a7e6", "#3fa9e1", "#96dee8", "#bf99f8"],
|
||||
color: ["#6be6c1", "#3fcfdc", "#626c91", "#3fbcde", "#a0a7e6", "#3fa9e1", "#96dee8", "#bf99f8"],
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
lineStyle: {
|
||||
color: "source",
|
||||
opacity: 0.12,
|
||||
opacity: 0.3,
|
||||
},
|
||||
tooltip: {
|
||||
position: "bottom",
|
||||
@ -75,8 +76,14 @@ limitations under the License. -->
|
||||
};
|
||||
}
|
||||
function linkTooltip(data: Call) {
|
||||
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
|
||||
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
|
||||
const clientMetrics: string[] =
|
||||
props.settings.metricMode === MetricModes.Expression
|
||||
? props.settings.linkClientExpressions
|
||||
: props.settings.linkClientMetrics;
|
||||
const serverMetrics: string[] =
|
||||
props.settings.metricMode === MetricModes.Expression
|
||||
? props.settings.linkServerExpressions
|
||||
: props.settings.linkServerMetrics;
|
||||
const linkServerMetricConfig: MetricConfigOpt[] = props.settings.linkServerMetricConfig || [];
|
||||
const linkClientMetricConfig: MetricConfigOpt[] = props.settings.linkClientMetricConfig || [];
|
||||
|
||||
@ -108,7 +115,10 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
function nodeTooltip(data: Node) {
|
||||
const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetricValue);
|
||||
const nodeMetrics: string[] =
|
||||
props.settings.metricMode === MetricModes.Expression
|
||||
? props.settings.nodeExpressions
|
||||
: props.settings.nodeMetrics;
|
||||
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
|
||||
const html = nodeMetrics.map((m, index) => {
|
||||
const metric =
|
||||
|
@ -13,6 +13,17 @@ 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 class="mt-20">
|
||||
<h5 class="title">{{ t("metricMode") }}</h5>
|
||||
<el-switch
|
||||
v-model="isExpression"
|
||||
class="mt-5"
|
||||
active-text="Expressions"
|
||||
inactive-text="General"
|
||||
size="small"
|
||||
@change="changeMetricMode"
|
||||
/>
|
||||
</div>
|
||||
<div class="link-settings">
|
||||
<h5 class="title">{{ t("callSettings") }}</h5>
|
||||
<div class="label">{{ t("linkDashboard") }}</div>
|
||||
@ -27,16 +38,34 @@ limitations under the License. -->
|
||||
/>
|
||||
<div class="label">
|
||||
<span>{{ t("linkServerMetrics") }}</span>
|
||||
<el-popover placement="left" :width="400" trigger="click" v-if="states.linkServerMetrics.length">
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
v-if="isExpression ? states.linkServerExpressions.length : states.linkServerMetrics.length"
|
||||
>
|
||||
<template #reference>
|
||||
<span @click="setConfigType('linkServerMetricConfig')">
|
||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Metrics :type="configType" :metrics="states.linkServerMetrics" @update="changeLinkServerMetrics" />
|
||||
<Metrics
|
||||
:type="configType"
|
||||
:metrics="isExpression ? states.linkServerExpressions : states.linkServerMetrics"
|
||||
@update="updateSettings"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<Tags
|
||||
:tags="states.linkServerExpressions"
|
||||
:vertical="true"
|
||||
:text="t('addExpressions')"
|
||||
@change="(param) => changeLinkServerExpressions(param)"
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
v-else
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
:value="states.linkServerMetrics"
|
||||
@ -48,16 +77,34 @@ limitations under the License. -->
|
||||
<span v-show="dashboardStore.entity !== EntityType[2].value">
|
||||
<div class="label">
|
||||
<span>{{ t("linkClientMetrics") }}</span>
|
||||
<el-popover placement="left" :width="400" trigger="click" v-if="states.linkClientMetrics.length">
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
v-if="isExpression ? states.linkClientExpressions.length : states.linkClientMetrics.length"
|
||||
>
|
||||
<template #reference>
|
||||
<span @click="setConfigType('linkClientMetricConfig')">
|
||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Metrics :type="configType" :metrics="states.linkClientMetrics" @update="changeLinkClientMetrics" />
|
||||
<Metrics
|
||||
:type="configType"
|
||||
:metrics="isExpression ? states.linkClientExpressions : states.linkClientMetrics"
|
||||
@update="updateSettings"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<Tags
|
||||
:tags="states.linkClientExpressions"
|
||||
:vertical="true"
|
||||
:text="t('addExpressions')"
|
||||
@change="(param) => changeLinkClientExpressions(param)"
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
v-else
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
:value="states.linkClientMetrics"
|
||||
@ -110,16 +157,34 @@ limitations under the License. -->
|
||||
</div>
|
||||
<div class="label">
|
||||
<span>{{ t("nodeMetrics") }}</span>
|
||||
<el-popover placement="left" :width="400" trigger="click" v-if="states.nodeMetrics.length">
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
v-if="isExpression ? states.nodeExpressions.length : states.nodeMetrics.length"
|
||||
>
|
||||
<template #reference>
|
||||
<span @click="setConfigType('nodeMetricConfig')">
|
||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Metrics :type="configType" :metrics="states.nodeMetrics" @update="changeNodeMetrics" />
|
||||
<Metrics
|
||||
:type="configType"
|
||||
:metrics="isExpression ? states.nodeExpressions : states.nodeMetrics"
|
||||
@update="updateSettings"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<Tags
|
||||
:tags="states.nodeExpressions"
|
||||
:vertical="true"
|
||||
:text="t('addExpressions')"
|
||||
@change="(param) => changeNodeExpressions(param)"
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
v-else
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
:value="states.nodeMetrics"
|
||||
@ -131,8 +196,26 @@ limitations under the License. -->
|
||||
</div>
|
||||
<div class="legend-settings" v-show="isService">
|
||||
<h5 class="title">{{ t("legendSettings") }}</h5>
|
||||
<div class="label">{{ t("conditions") }}</div>
|
||||
<div v-for="(metric, index) of legend.metric" :key="metric.name + index">
|
||||
<span v-if="isExpression">
|
||||
<div class="label">Healthy Description</div>
|
||||
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||
</span>
|
||||
<div class="label">
|
||||
<span>{{ t(isExpression ? "unhealthyExpression" : "conditions") }}</span>
|
||||
<el-tooltip
|
||||
class="cp"
|
||||
v-if="isExpression"
|
||||
content="The node would be red to indicate unhealthy status when the expression return greater than 0"
|
||||
>
|
||||
<span>
|
||||
<Icon class="icon-help ml-5" iconName="help" size="small" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<el-input v-model="legendMQE.expression" placeholder="Please input a expression" size="small" class="inputs" />
|
||||
</div>
|
||||
<div v-for="(metric, index) of legend" :key="index" v-else>
|
||||
<Selector
|
||||
class="item"
|
||||
:value="metric.name"
|
||||
@ -163,14 +246,16 @@ limitations under the License. -->
|
||||
class="cp"
|
||||
iconName="add_circle_outlinecontrol_point"
|
||||
size="middle"
|
||||
v-show="index === legend.metric.length - 1 && legend.metric.length < 5"
|
||||
v-show="index === legend.length - 1 && legend.length < 5"
|
||||
@click="addMetric"
|
||||
/>
|
||||
</span>
|
||||
<div v-show="index !== legend.metric.length - 1">&&</div>
|
||||
<div v-show="index !== legend.length - 1">&&</div>
|
||||
</div>
|
||||
<div class="label">Healthy Description</div>
|
||||
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||
<span v-if="!isExpression">
|
||||
<div class="label">Healthy Description</div>
|
||||
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||
</span>
|
||||
<div class="label">Unhealthy Description</div>
|
||||
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||
<el-button @click="setLegend" class="legend-btn" size="small" type="primary">
|
||||
@ -184,12 +269,20 @@ limitations under the License. -->
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
|
||||
import {
|
||||
MetricCatalog,
|
||||
ScopeType,
|
||||
MetricConditions,
|
||||
EntityType,
|
||||
LegendOpt,
|
||||
MetricsType,
|
||||
MetricModes,
|
||||
} from "../../../data";
|
||||
import type { Option } from "@/types/app";
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import type { Node } from "@/types/topology";
|
||||
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||
import { EntityType, LegendOpt, MetricsType } from "../../../data";
|
||||
import Metrics from "./Metrics.vue";
|
||||
|
||||
/*global defineEmits */
|
||||
@ -198,6 +291,7 @@ limitations under the License. -->
|
||||
const dashboardStore = useDashboardStore();
|
||||
const topologyStore = useTopologyStore();
|
||||
const { selectedGrid } = dashboardStore;
|
||||
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
|
||||
const nodeDashboard =
|
||||
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length ? selectedGrid.nodeDashboard : "";
|
||||
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
|
||||
@ -220,6 +314,9 @@ limitations under the License. -->
|
||||
linkMetricList: Option[];
|
||||
linkDashboards: (DashboardItem & { label: string; value: string })[];
|
||||
nodeDashboards: (DashboardItem & { label: string; value: string })[];
|
||||
linkServerExpressions: string[];
|
||||
linkClientExpressions: string[];
|
||||
nodeExpressions: string[];
|
||||
}>({
|
||||
linkDashboard: selectedGrid.linkDashboard || "",
|
||||
nodeDashboard: selectedGrid.nodeDashboard || [],
|
||||
@ -230,13 +327,15 @@ limitations under the License. -->
|
||||
linkMetricList: [],
|
||||
linkDashboards: [],
|
||||
nodeDashboards: [],
|
||||
linkServerExpressions: selectedGrid.linkServerExpressions || [],
|
||||
linkClientExpressions: selectedGrid.linkClientExpressions || [],
|
||||
nodeExpressions: selectedGrid.nodeExpressions || [],
|
||||
});
|
||||
const l = selectedGrid.legend && selectedGrid.legend.length;
|
||||
const legend = reactive<{
|
||||
metric: { name: string; condition: string; value: string }[];
|
||||
}>({
|
||||
metric: l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
||||
});
|
||||
const legend = ref<{ name: string; condition: string; value: string }[]>(
|
||||
l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
||||
);
|
||||
const legendMQE = ref<{ expression: string }>(selectedGrid.legendMQE || { expression: "" });
|
||||
const configType = ref<string>("");
|
||||
const description = reactive<any>(selectedGrid.description || {});
|
||||
|
||||
@ -285,18 +384,35 @@ limitations under the License. -->
|
||||
}
|
||||
async function setLegend() {
|
||||
updateSettings();
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name);
|
||||
if (!names.length) {
|
||||
emit("updateNodes");
|
||||
return;
|
||||
}
|
||||
const param = await useQueryTopologyMetrics(names, ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
if (isExpression.value) {
|
||||
const expression = dashboardStore.selectedGrid.legendMQE && dashboardStore.selectedGrid.legendMQE.expression;
|
||||
if (!expression) {
|
||||
emit("updateNodes");
|
||||
return;
|
||||
}
|
||||
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
|
||||
const param = getExpressionQuery();
|
||||
const res = await topologyStore.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
} else {
|
||||
topologyStore.setLegendValues([expression], res.data);
|
||||
}
|
||||
} else {
|
||||
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name && d.condition && d.value);
|
||||
if (!names.length) {
|
||||
emit("updateNodes");
|
||||
return;
|
||||
}
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
const param = await useQueryTopologyMetrics(names, ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
|
||||
emit("updateNodes");
|
||||
}
|
||||
function changeNodeDashboard(opt: any) {
|
||||
@ -308,7 +424,7 @@ limitations under the License. -->
|
||||
updateSettings();
|
||||
}
|
||||
function changeLegend(type: string, opt: any, index: number) {
|
||||
(legend.metric[index] as any)[type] = opt[0].value || opt;
|
||||
(legend.value[index] as any)[type] = opt[0].value || opt;
|
||||
}
|
||||
function changeScope(index: number, opt: Option[] | any) {
|
||||
items[index].scope = opt[0].value;
|
||||
@ -340,7 +456,13 @@ limitations under the License. -->
|
||||
updateSettings();
|
||||
}
|
||||
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
|
||||
const metrics = legend.metric.filter((d: any) => d.name && d.value && d.condition);
|
||||
let metrics = [];
|
||||
if (isExpression.value) {
|
||||
metrics = legend.value.filter((d: any) => d.name);
|
||||
} else {
|
||||
metrics = legend.value.filter((d: any) => d.name && d.value && d.condition);
|
||||
}
|
||||
|
||||
const param = {
|
||||
...dashboardStore.selectedGrid,
|
||||
linkDashboard: states.linkDashboard,
|
||||
@ -350,7 +472,12 @@ limitations under the License. -->
|
||||
linkServerMetrics: states.linkServerMetrics,
|
||||
linkClientMetrics: states.linkClientMetrics,
|
||||
nodeMetrics: states.nodeMetrics,
|
||||
linkServerExpressions: states.linkServerExpressions,
|
||||
linkClientExpressions: states.linkClientExpressions,
|
||||
nodeExpressions: states.nodeExpressions,
|
||||
metricMode: isExpression.value ? MetricModes.Expression : MetricModes.General,
|
||||
legend: metrics,
|
||||
legendMQE: legendMQE.value,
|
||||
...metricConfig,
|
||||
description,
|
||||
};
|
||||
@ -378,6 +505,30 @@ limitations under the License. -->
|
||||
}
|
||||
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
|
||||
}
|
||||
function changeLinkServerExpressions(param: string[]) {
|
||||
if (!isExpression.value) {
|
||||
return;
|
||||
}
|
||||
states.linkServerExpressions = param;
|
||||
updateSettings();
|
||||
if (!states.linkServerExpressions.length) {
|
||||
topologyStore.setLinkServerMetrics({});
|
||||
return;
|
||||
}
|
||||
topologyStore.getLinkExpressions(states.linkServerExpressions, "SERVER");
|
||||
}
|
||||
function changeLinkClientExpressions(param: string[]) {
|
||||
if (!isExpression.value) {
|
||||
return;
|
||||
}
|
||||
states.linkClientExpressions = param;
|
||||
updateSettings();
|
||||
if (!states.linkClientExpressions.length) {
|
||||
topologyStore.changeLinkClientMetrics({});
|
||||
return;
|
||||
}
|
||||
topologyStore.getLinkExpressions(states.linkClientExpressions, "CLIENT");
|
||||
}
|
||||
function updateLinkClientMetrics(options: Option[] | any) {
|
||||
const opt = options.map((d: Option) => d.value);
|
||||
const index = states.linkClientMetrics.findIndex((d: any) => !opt.includes(d));
|
||||
@ -419,18 +570,49 @@ limitations under the License. -->
|
||||
topologyStore.queryNodeMetrics(states.nodeMetrics);
|
||||
}
|
||||
function deleteMetric(index: number) {
|
||||
if (legend.metric.length === 1) {
|
||||
legend.metric = [{ name: "", condition: "", value: "" }];
|
||||
if (legend.value.length === 1) {
|
||||
legend.value = [{ name: "", condition: "", value: "" }];
|
||||
return;
|
||||
}
|
||||
legend.metric.splice(index, 1);
|
||||
legend.value.splice(index, 1);
|
||||
}
|
||||
function addMetric() {
|
||||
legend.metric.push({ name: "", condition: "", value: "" });
|
||||
legend.value.push({ name: "", condition: "", value: "" });
|
||||
}
|
||||
function setConfigType(type: string) {
|
||||
configType.value = type;
|
||||
}
|
||||
function changeNodeExpressions(param: string[]) {
|
||||
if (!isExpression.value) {
|
||||
return;
|
||||
}
|
||||
states.nodeExpressions = param;
|
||||
updateSettings();
|
||||
if (!states.nodeExpressions.length) {
|
||||
topologyStore.setNodeMetricValue({});
|
||||
return;
|
||||
}
|
||||
topologyStore.queryNodeExpressions(states.nodeExpressions);
|
||||
}
|
||||
function changeMetricMode() {
|
||||
legend.value = [{ name: "", condition: "", value: "" }];
|
||||
const config = {
|
||||
linkServerMetricConfig: [],
|
||||
linkClientMetricConfig: [],
|
||||
nodeMetricConfig: [],
|
||||
};
|
||||
if (isExpression.value) {
|
||||
states.linkServerMetrics = [];
|
||||
states.linkClientMetrics = [];
|
||||
states.nodeMetrics = [];
|
||||
} else {
|
||||
states.linkServerExpressions = [];
|
||||
states.linkClientExpressions = [];
|
||||
states.nodeExpressions = [];
|
||||
}
|
||||
|
||||
updateSettings(config);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.link-settings {
|
||||
@ -442,6 +624,11 @@ limitations under the License. -->
|
||||
width: 355px;
|
||||
}
|
||||
|
||||
.legend-inputs {
|
||||
margin-top: 8px;
|
||||
width: 310px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 130px;
|
||||
margin-top: 5px;
|
||||
@ -453,13 +640,14 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
color: #666;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: $font-size-smaller;
|
||||
margin-top: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.legend-btn {
|
||||
|
Loading…
Reference in New Issue
Block a user