feat: Implement custom configurations for metrics on dashboards and topology (#39)
17
src/assets/icons/arrow_drop_down.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<!-- 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. -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M6.984 9.984h10.031l-5.016 5.016z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 922 B |
@ -13,6 +13,5 @@ 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. -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<title>cancel</title>
|
||||
<path d="M17.016 15.609l-3.609-3.609 3.609-3.609-1.406-1.406-3.609 3.609-3.609-3.609-1.406 1.406 3.609 3.609-3.609 3.609 1.406 1.406 3.609-3.609 3.609 3.609zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -13,6 +13,5 @@ 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. -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<title>clearclose</title>
|
||||
<path d="M18.984 6.422l-5.578 5.578 5.578 5.578-1.406 1.406-5.578-5.578-5.578 5.578-1.406-1.406 5.578-5.578-5.578-5.578 1.406-1.406 5.578 5.578 5.578-5.578z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -13,6 +13,5 @@ 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. -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<title>createmode_editedit</title>
|
||||
<path d="M20.719 7.031l-1.828 1.828-3.75-3.75 1.828-1.828q0.281-0.281 0.703-0.281t0.703 0.281l2.344 2.344q0.281 0.281 0.281 0.703t-0.281 0.703zM3 17.25l11.063-11.063 3.75 3.75-11.063 11.063h-3.75v-3.75z"></path>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
177
src/components/Select.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<!-- 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 class="bar-select cp flex-h" :class="{ active: visible }">
|
||||
<div class="bar-i" @click="setPopper">
|
||||
<span v-if="selected.value">
|
||||
{{ selected.label }}
|
||||
</span>
|
||||
<span class="no-data" v-else>Please select a option</span>
|
||||
<span class="remove-icon" @click="removeSelected" v-if="clearable">
|
||||
×
|
||||
</span>
|
||||
</div>
|
||||
<div class="opt-wrapper" v-show="visible">
|
||||
<div
|
||||
class="opt ell"
|
||||
@click="handleSelect(i)"
|
||||
:class="{ 'select-disabled': selected.value === i.value }"
|
||||
v-for="i in options"
|
||||
:key="i.value"
|
||||
>
|
||||
{{ i.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { Option } from "@/types/app";
|
||||
|
||||
/*global defineProps, defineEmits*/
|
||||
const emit = defineEmits(["change"]);
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array as PropType<(Option & { disabled: boolean })[]>,
|
||||
default: () => [],
|
||||
},
|
||||
value: {
|
||||
type: String as PropType<string>,
|
||||
default: () => "",
|
||||
},
|
||||
clearable: { type: Boolean, default: false },
|
||||
});
|
||||
const visible = ref<boolean>(false);
|
||||
const opt = props.options.find((d: Option) => props.value === d.value);
|
||||
const selected = ref<Option>(opt || { label: "", value: "" });
|
||||
|
||||
function handleSelect(i: Option) {
|
||||
selected.value = i;
|
||||
emit("change", i.value);
|
||||
}
|
||||
function removeSelected() {
|
||||
selected.value = { label: "", value: "" };
|
||||
emit("change", "");
|
||||
}
|
||||
watch(
|
||||
() => props.value,
|
||||
(data) => {
|
||||
const opt = props.options.find((d: Option) => data === d.value);
|
||||
selected.value = opt || { label: "", value: "" };
|
||||
}
|
||||
);
|
||||
document.body.addEventListener("click", handleClick, false);
|
||||
|
||||
function handleClick() {
|
||||
visible.value = false;
|
||||
}
|
||||
function setPopper(event: any) {
|
||||
event.stopPropagation();
|
||||
visible.value = !visible.value;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.bar-select {
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #ddd;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
color: #000;
|
||||
font-size: 12px;
|
||||
height: 24px;
|
||||
|
||||
.selected {
|
||||
padding: 0 3px;
|
||||
border-radius: 3px;
|
||||
margin: 3px;
|
||||
color: #409eff;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #e8e8e8;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.bar-i {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 2px 10px;
|
||||
overflow: auto;
|
||||
color: #606266;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.remove-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 0;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.opt-wrapper {
|
||||
color: #606266;
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
left: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 6px rgba(99, 99, 99, 0.2);
|
||||
border: 1px solid #ddd;
|
||||
width: 100%;
|
||||
border-radius: 0 0 3px 3px;
|
||||
border-right-width: 1px !important;
|
||||
z-index: 10;
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 12px;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.opt {
|
||||
padding: 7px 15px;
|
||||
|
||||
&.select-disabled {
|
||||
color: #409eff;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -19,6 +19,7 @@ import TimePicker from "./TimePicker.vue";
|
||||
import Selector from "./Selector.vue";
|
||||
import Graph from "./Graph.vue";
|
||||
import Radio from "./Radio.vue";
|
||||
import SelectSingle from "./Select.vue";
|
||||
import type { App } from "vue";
|
||||
import VueGridLayout from "vue-grid-layout";
|
||||
|
||||
@ -29,6 +30,7 @@ const components: { [key: string]: any } = {
|
||||
Selector,
|
||||
Graph,
|
||||
Radio,
|
||||
SelectSingle,
|
||||
};
|
||||
const componentsName: string[] = Object.keys(components);
|
||||
|
||||
|
@ -22,6 +22,15 @@ export enum MetricQueryTypes {
|
||||
READHEATMAP = "readHeatMap",
|
||||
ReadSampledRecords = "readSampledRecords",
|
||||
}
|
||||
|
||||
export enum Calculations {
|
||||
Percentage = "percentage",
|
||||
ByteToKB = "byteToKB",
|
||||
Apdex = "apdex",
|
||||
Precision = "precision",
|
||||
ConvertSeconds = "convertSeconds",
|
||||
ConvertMilliseconds = "convertMilliseconds",
|
||||
}
|
||||
export enum sizeEnum {
|
||||
XS = "XS",
|
||||
SM = "SM",
|
||||
|
@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import dayjs from "dayjs";
|
||||
import { RespFields, MetricQueryTypes } from "./data";
|
||||
import { RespFields, MetricQueryTypes, Calculations } from "./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 { Instance, Endpoint, Service } from "@/types/selector";
|
||||
import { StandardConfig } from "@/types/dashboard";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
|
||||
export function useQueryProcessor(config: any) {
|
||||
if (!(config.metrics && config.metrics[0])) {
|
||||
@ -48,6 +48,7 @@ export function useQueryProcessor(config: any) {
|
||||
}
|
||||
const fragment = config.metrics.map((name: string, index: number) => {
|
||||
const metricType = config.metricTypes[index] || "";
|
||||
const c = (config.metricConfig && config.metricConfig[index]) || {};
|
||||
if (
|
||||
[
|
||||
MetricQueryTypes.ReadSampledRecords,
|
||||
@ -63,11 +64,11 @@ export function useQueryProcessor(config: any) {
|
||||
normal: selectorStore.currentService.normal,
|
||||
scope: dashboardStore.entity,
|
||||
topN: 10,
|
||||
order: config.standard.sortOrder || "DES",
|
||||
order: c.sortOrder || "DES",
|
||||
};
|
||||
} else {
|
||||
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
|
||||
const labels = (config.labelsIndex || "")
|
||||
const labels = (c.labelsIndex || "")
|
||||
.split(",")
|
||||
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
||||
variables.push(`$labels${index}: [String!]!`);
|
||||
@ -128,7 +129,7 @@ export function useSourceProcessor(
|
||||
config: {
|
||||
metrics: string[];
|
||||
metricTypes: string[];
|
||||
standard: StandardConfig;
|
||||
metricConfig: MetricConfigOpt[];
|
||||
}
|
||||
) {
|
||||
if (resp.errors) {
|
||||
@ -140,23 +141,24 @@ export function useSourceProcessor(
|
||||
|
||||
config.metricTypes.forEach((type: string, index) => {
|
||||
const m = config.metrics[index];
|
||||
const c = (config.metricConfig && config.metricConfig[index]) || {};
|
||||
|
||||
if (type === MetricQueryTypes.ReadMetricsValues) {
|
||||
source[m] = resp.data[keys[index]].values.values.map(
|
||||
(d: { value: number }) => aggregation(d.value, config.standard)
|
||||
(d: { value: number }) => aggregation(d.value, c)
|
||||
);
|
||||
}
|
||||
if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
|
||||
const resVal = Object.values(resp.data)[0] || [];
|
||||
const labels = (config.standard.metricLabels || "")
|
||||
const labels = (c.label || "")
|
||||
.split(",")
|
||||
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
||||
const labelsIdx = (config.standard.labelsIndex || "")
|
||||
const labelsIdx = (c.labelsIndex || "")
|
||||
.split(",")
|
||||
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
||||
for (const item of resVal) {
|
||||
const values = item.values.values.map((d: { value: number }) =>
|
||||
aggregation(Number(d.value), config.standard)
|
||||
aggregation(Number(d.value), c)
|
||||
);
|
||||
|
||||
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
|
||||
@ -168,10 +170,7 @@ export function useSourceProcessor(
|
||||
}
|
||||
}
|
||||
if (type === MetricQueryTypes.ReadMetricsValue) {
|
||||
source[m] = aggregation(
|
||||
Number(Object.values(resp.data)[0]),
|
||||
config.standard
|
||||
);
|
||||
source[m] = aggregation(Number(Object.values(resp.data)[0]), c);
|
||||
}
|
||||
if (
|
||||
type === MetricQueryTypes.SortMetrics ||
|
||||
@ -179,7 +178,7 @@ export function useSourceProcessor(
|
||||
) {
|
||||
source[m] = (Object.values(resp.data)[0] || []).map(
|
||||
(d: { value: unknown; name: string }) => {
|
||||
d.value = aggregation(Number(d.value), config.standard);
|
||||
d.value = aggregation(Number(d.value), c);
|
||||
|
||||
return d;
|
||||
}
|
||||
@ -258,7 +257,11 @@ export function useQueryPodsMetrics(
|
||||
export function usePodsSource(
|
||||
pods: Array<Instance | Endpoint>,
|
||||
resp: { errors: string; data: { [key: string]: any } },
|
||||
config: { metrics: string[]; metricTypes: string[] }
|
||||
config: {
|
||||
metrics: string[];
|
||||
metricTypes: string[];
|
||||
metricConfig: MetricConfigOpt[];
|
||||
}
|
||||
): any {
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
@ -266,13 +269,14 @@ export function usePodsSource(
|
||||
}
|
||||
const data = pods.map((d: Instance | any, idx: number) => {
|
||||
config.metrics.map((name: string, index: number) => {
|
||||
const c = (config.metricConfig && config.metricConfig[index]) || {};
|
||||
const key = name + idx + index;
|
||||
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
|
||||
d[name] = resp.data[key];
|
||||
d[name] = aggregation(resp.data[key], c);
|
||||
}
|
||||
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
|
||||
d[name] = resp.data[key].values.values.map(
|
||||
(d: { value: number }) => d.value
|
||||
d[name] = resp.data[key].values.values.map((d: { value: number }) =>
|
||||
aggregation(d.value, c)
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -307,32 +311,31 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
|
||||
return { queryStr, conditions };
|
||||
}
|
||||
|
||||
function aggregation(val: number, standard: any): number | string {
|
||||
let data: number | string = val;
|
||||
export function aggregation(val: number, config: any): number | string {
|
||||
let data: number | string = Number(val);
|
||||
|
||||
if (!isNaN(standard.plus)) {
|
||||
data = val + Number(standard.plus);
|
||||
return data;
|
||||
}
|
||||
if (!isNaN(standard.minus)) {
|
||||
data = val - Number(standard.plus);
|
||||
return data;
|
||||
}
|
||||
if (!isNaN(standard.multiply) && standard.divide !== 0) {
|
||||
data = val * Number(standard.multiply);
|
||||
return data;
|
||||
}
|
||||
if (!isNaN(standard.divide) && standard.divide !== 0) {
|
||||
data = val / Number(standard.divide);
|
||||
return data;
|
||||
}
|
||||
if (standard.milliseconds) {
|
||||
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
|
||||
return data;
|
||||
}
|
||||
if (standard.milliseconds) {
|
||||
data = dayjs.unix(val).format("YYYY-MM-DD HH:mm:ss");
|
||||
return data;
|
||||
switch (config.calculation) {
|
||||
case Calculations.Percentage:
|
||||
data = val / 100;
|
||||
break;
|
||||
case Calculations.ByteToKB:
|
||||
data = val / 1024;
|
||||
break;
|
||||
case Calculations.Apdex:
|
||||
data = val / 10000;
|
||||
break;
|
||||
case Calculations.ConvertSeconds:
|
||||
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
|
||||
break;
|
||||
case Calculations.ConvertMilliseconds:
|
||||
data = dayjs.unix(val).format("YYYY-MM-DD HH:mm:ss");
|
||||
break;
|
||||
case Calculations.Precision:
|
||||
data = data.toFixed(2);
|
||||
break;
|
||||
default:
|
||||
data;
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -126,6 +126,9 @@ const msg = {
|
||||
kubernetesCluster: "Cluster",
|
||||
kubernetes: "Kubernetes",
|
||||
textUrl: "Text Hyperlink",
|
||||
textAlign: "Text Align",
|
||||
metricLabel: "Metric Label",
|
||||
showUnit: "Show Unit",
|
||||
hourTip: "Select Hour",
|
||||
minuteTip: "Select Minute",
|
||||
secondTip: "Select Second",
|
||||
@ -258,7 +261,7 @@ const msg = {
|
||||
independentSelector: "Selectors",
|
||||
unknownMetrics: "Unknown Metrics",
|
||||
labels: "Labels",
|
||||
aggregation: "Data Calculation",
|
||||
aggregation: "Calculation",
|
||||
unit: "Unit",
|
||||
labelsIndex: "Label Subscript",
|
||||
parentService: "Parent Service",
|
||||
|
@ -126,6 +126,9 @@ const msg = {
|
||||
kubernetesCluster: "集群",
|
||||
kubernetes: "Kubernetes",
|
||||
textUrl: "文本超链接",
|
||||
textAlign: "文本对齐",
|
||||
metricLabel: "指标标签",
|
||||
showUnit: "显示单位",
|
||||
hourTip: "选择小时",
|
||||
minuteTip: "选择分钟",
|
||||
secondTip: "选择秒数",
|
||||
@ -260,7 +263,7 @@ const msg = {
|
||||
independentSelector: "独立选择器",
|
||||
unknownMetrics: "未知指标",
|
||||
labels: "标签",
|
||||
aggregation: "数据计算",
|
||||
aggregation: "计算",
|
||||
unit: "单位",
|
||||
labelsIndex: "标签下标",
|
||||
parentService: "父级服务",
|
||||
|
@ -28,6 +28,7 @@ export const NewControl = {
|
||||
standard: {},
|
||||
metrics: [""],
|
||||
metricTypes: [""],
|
||||
metricConfig: [],
|
||||
};
|
||||
export const TextConfig = {
|
||||
fontColor: "white",
|
||||
|
@ -36,8 +36,17 @@ export interface LayoutConfig {
|
||||
metricTypes: string[];
|
||||
children?: any;
|
||||
activedTabIndex?: number;
|
||||
metricConfig?: MetricConfigOpt[];
|
||||
}
|
||||
|
||||
export type MetricConfigOpt = {
|
||||
unit: string;
|
||||
label: string;
|
||||
calculation: string;
|
||||
labelsIndex: string;
|
||||
sortOrder: string;
|
||||
};
|
||||
|
||||
export interface WidgetConfig {
|
||||
title?: string;
|
||||
tips?: string;
|
||||
@ -87,7 +96,7 @@ export interface AreaConfig {
|
||||
export interface CardConfig {
|
||||
type?: string;
|
||||
fontSize?: number;
|
||||
showUint?: boolean;
|
||||
showUnit?: boolean;
|
||||
textAlign?: "center" | "right" | "left";
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,17 @@ limitations under the License. -->
|
||||
@change="changeConfig({ content })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("textAlign") }}</span>
|
||||
<Selector
|
||||
:value="textAlign"
|
||||
:options="AlignStyle"
|
||||
size="small"
|
||||
placeholder="Select a color"
|
||||
class="input"
|
||||
@change="changeConfig({ textAlign: $event[0].value })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("backgroundColors") }}</span>
|
||||
<Selector
|
||||
@ -85,6 +96,7 @@ const backgroundColor = ref(originConfig.graph.backgroundColor || "green");
|
||||
const fontColor = ref(originConfig.graph.fontColor || "white");
|
||||
const content = ref<string>(originConfig.graph.content || "");
|
||||
const fontSize = ref<number>(originConfig.graph.fontSize || 12);
|
||||
const textAlign = ref(originConfig.graph.textAlign || "left");
|
||||
const Colors = [
|
||||
{
|
||||
label: "Green",
|
||||
@ -97,6 +109,14 @@ const Colors = [
|
||||
{ label: "Black", value: "black" },
|
||||
{ label: "Orange", value: "orange" },
|
||||
];
|
||||
const AlignStyle = [
|
||||
{
|
||||
label: "Left",
|
||||
value: "left",
|
||||
},
|
||||
{ label: "Center", value: "center" },
|
||||
{ label: "Right", value: "right" },
|
||||
];
|
||||
function changeConfig(param: { [key: string]: unknown }) {
|
||||
const { selectedGrid } = dashboardStore;
|
||||
const graph = {
|
||||
|
@ -36,7 +36,7 @@ limitations under the License. -->
|
||||
i: dashboardStore.selectedGrid.i,
|
||||
metrics: dashboardStore.selectedGrid.metrics,
|
||||
metricTypes: dashboardStore.selectedGrid.metricTypes,
|
||||
standard: dashboardStore.selectedGrid.standard,
|
||||
metricConfig: dashboardStore.selectedGrid.metricConfig,
|
||||
}"
|
||||
:isEdit="isEdit"
|
||||
@changeOpt="setStatus"
|
||||
@ -64,9 +64,6 @@ limitations under the License. -->
|
||||
<el-collapse-item :title="t('widgetOptions')" name="3">
|
||||
<WidgetOptions />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item :title="t('standardOptions')" name="4">
|
||||
<StandardOptions @update="getSource" @loading="setLoading" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<div class="footer">
|
||||
@ -88,16 +85,14 @@ import { Option } from "@/types/app";
|
||||
import graphs from "../graphs";
|
||||
import configs from "./widget/graph-styles";
|
||||
import WidgetOptions from "./widget/WidgetOptions.vue";
|
||||
import StandardOptions from "./widget/StandardOptions.vue";
|
||||
import MetricOptions from "./widget/MetricOptions.vue";
|
||||
import MetricOptions from "./widget/metric/Index.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ConfigEdit",
|
||||
name: "WidgetEdit",
|
||||
components: {
|
||||
...graphs,
|
||||
...configs,
|
||||
WidgetOptions,
|
||||
StandardOptions,
|
||||
MetricOptions,
|
||||
},
|
||||
setup() {
|
||||
|
@ -1,186 +0,0 @@
|
||||
<!-- 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 class="item">
|
||||
<span class="label">{{ t("unit") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.unit"
|
||||
size="small"
|
||||
placeholder="Please input Unit"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("sortOrder") }}</span>
|
||||
<Selector
|
||||
:value="sortOrder"
|
||||
:options="SortOrder"
|
||||
size="small"
|
||||
placeholder="Select a sort order"
|
||||
class="selector"
|
||||
@change="changeStandardOpt({ sortOrder })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("labels") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.metricLabels"
|
||||
size="small"
|
||||
placeholder="auto"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("labelsIndex") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.labelsIndex"
|
||||
size="small"
|
||||
placeholder="auto"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("plus") }}</span>
|
||||
<el-input-number
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.plus"
|
||||
:min="0"
|
||||
size="small"
|
||||
placeholder="Please input"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("minus") }}</span>
|
||||
<el-input-number
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.minus"
|
||||
:min="0"
|
||||
size="small"
|
||||
placeholder="Please input"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("multiply") }}</span>
|
||||
<el-input-number
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.multiply"
|
||||
:min="1"
|
||||
size="small"
|
||||
placeholder="Please input"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("divide") }}</span>
|
||||
<el-input-number
|
||||
class="input"
|
||||
v-model="selectedGrid.standard.divide"
|
||||
size="small"
|
||||
placeholder="Please input"
|
||||
:min="1"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("convertToMilliseconds") }}</span>
|
||||
<el-input-number
|
||||
class="input"
|
||||
:min="0"
|
||||
v-model="selectedGrid.standard.milliseconds"
|
||||
size="small"
|
||||
placeholder="Please input"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("convertToSeconds") }}</span>
|
||||
<el-input-number
|
||||
class="input"
|
||||
:min="0"
|
||||
v-model="selectedGrid.standard.seconds"
|
||||
size="small"
|
||||
placeholder="Please input"
|
||||
@change="changeStandardOpt"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { SortOrder } from "../../data";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
/*global defineEmits */
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(["update", "loading"]);
|
||||
const dashboardStore = useDashboardStore();
|
||||
const { selectedGrid } = dashboardStore;
|
||||
const sortOrder = ref<string>(selectedGrid.standard.sortOrder || "DES");
|
||||
|
||||
function changeStandardOpt(param?: any) {
|
||||
let standard = dashboardStore.selectedGrid.standard;
|
||||
if (param) {
|
||||
standard = {
|
||||
...dashboardStore.selectedGrid.standard,
|
||||
...param,
|
||||
};
|
||||
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, standard });
|
||||
}
|
||||
queryMetrics();
|
||||
}
|
||||
async function queryMetrics() {
|
||||
const params = useQueryProcessor(dashboardStore.selectedGrid);
|
||||
if (!params) {
|
||||
emit("update", {});
|
||||
return;
|
||||
}
|
||||
|
||||
emit("loading", true);
|
||||
const json = await dashboardStore.fetchMetricValue(params);
|
||||
emit("loading", false);
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return;
|
||||
}
|
||||
const source = useSourceProcessor(json, dashboardStore.selectedGrid);
|
||||
emit("update", source);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.selector {
|
||||
width: 500px;
|
||||
}
|
||||
</style>
|
@ -26,6 +26,15 @@ limitations under the License. -->
|
||||
@change="updateConfig({ fontSize })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("showUnit") }}</span>
|
||||
<el-switch
|
||||
v-model="showUnit"
|
||||
active-text="Yes"
|
||||
inactive-text="No"
|
||||
@change="updateConfig({ showUnit })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
@ -36,13 +45,14 @@ const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const { selectedGrid } = dashboardStore;
|
||||
const fontSize = ref(selectedGrid.graph.fontSize);
|
||||
const showUnit = ref<boolean>(selectedGrid.graph.showUnit);
|
||||
|
||||
function updateConfig(param: { [key: string]: unknown }) {
|
||||
const graph = {
|
||||
...selectedGrid.graph,
|
||||
...dashboardStore.selectedGrid.graph,
|
||||
...param,
|
||||
};
|
||||
dashboardStore.selectWidget({ ...selectedGrid, graph });
|
||||
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
@ -49,6 +49,18 @@ limitations under the License. -->
|
||||
@change="changeMetricType(index, $event)"
|
||||
class="selectors"
|
||||
/>
|
||||
<el-popover placement="top" :width="400" trigger="click">
|
||||
<template #reference>
|
||||
<span @click="setMetricConfig(index)">
|
||||
<Icon class="cp mr-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Standard
|
||||
@update="queryMetrics"
|
||||
:currentMetricConfig="currentMetricConfig"
|
||||
:index="index"
|
||||
/>
|
||||
</el-popover>
|
||||
<span
|
||||
v-show="states.isList || states.metricTypes[0] === 'readMetricsValues'"
|
||||
>
|
||||
@ -96,12 +108,13 @@ import {
|
||||
ChartTypes,
|
||||
PodsChartTypes,
|
||||
MetricsType,
|
||||
} from "../../data";
|
||||
} from "../../../data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Icon from "@/components/Icon.vue";
|
||||
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { DashboardItem } from "@/types/dashboard";
|
||||
import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||
import Standard from "./Standard.vue";
|
||||
|
||||
/*global defineEmits */
|
||||
const { t } = useI18n();
|
||||
@ -127,6 +140,13 @@ const states = reactive<{
|
||||
dashboardName: graph.dashboardName,
|
||||
dashboardList: [{ label: "", value: "" }],
|
||||
});
|
||||
const currentMetricConfig = ref<MetricConfigOpt>({
|
||||
unit: "",
|
||||
label: "",
|
||||
labelsIndex: "",
|
||||
calculation: "",
|
||||
sortOrder: "DES",
|
||||
});
|
||||
|
||||
states.isList = ListChartTypes.includes(graph.type);
|
||||
const defaultLen = ref<number>(states.isList ? 5 : 20);
|
||||
@ -323,8 +343,8 @@ async function queryMetrics() {
|
||||
if (states.isList) {
|
||||
return;
|
||||
}
|
||||
const { standard } = dashboardStore.selectedGrid;
|
||||
const params = useQueryProcessor({ ...states, standard });
|
||||
const { metricConfig } = dashboardStore.selectedGrid;
|
||||
const params = useQueryProcessor({ ...states, metricConfig });
|
||||
if (!params) {
|
||||
emit("update", {});
|
||||
return;
|
||||
@ -337,7 +357,7 @@ async function queryMetrics() {
|
||||
ElMessage.error(json.errors);
|
||||
return;
|
||||
}
|
||||
const source = useSourceProcessor(json, { ...states, standard });
|
||||
const source = useSourceProcessor(json, { ...states, metricConfig });
|
||||
emit("update", source);
|
||||
}
|
||||
|
||||
@ -372,11 +392,20 @@ function deleteMetric(index: number) {
|
||||
dashboardStore.selectWidget({
|
||||
...dashboardStore.selectedGrid,
|
||||
...{ metricTypes: states.metricTypes, metrics: states.metrics },
|
||||
metricConfig: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
states.metrics.splice(index, 1);
|
||||
states.metricTypes.splice(index, 1);
|
||||
const config = dashboardStore.selectedGrid.metricConfig || [];
|
||||
const metricConfig = config[index] ? config.splice(index, 1) : config;
|
||||
dashboardStore.selectWidget({
|
||||
...dashboardStore.selectedGrid,
|
||||
...{ metricTypes: states.metricTypes, metrics: states.metrics },
|
||||
metricConfig,
|
||||
});
|
||||
console.log(dashboardStore.selectedGrid);
|
||||
}
|
||||
function setMetricTypeList(type: string) {
|
||||
if (type !== MetricsType.REGULAR_VALUE) {
|
||||
@ -393,6 +422,26 @@ function setMetricTypeList(type: string) {
|
||||
}
|
||||
return MetricTypes[type];
|
||||
}
|
||||
function setMetricConfig(index: number) {
|
||||
const n = {
|
||||
unit: "",
|
||||
label: "",
|
||||
calculation: "",
|
||||
labelsIndex: "",
|
||||
sortOrder: "DES",
|
||||
};
|
||||
if (
|
||||
!dashboardStore.selectedGrid.metricConfig ||
|
||||
!dashboardStore.selectedGrid.metricConfig[index]
|
||||
) {
|
||||
currentMetricConfig.value = n;
|
||||
return;
|
||||
}
|
||||
currentMetricConfig.value = {
|
||||
...n,
|
||||
...dashboardStore.selectedGrid.metricConfig[index],
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ds-name {
|
129
src/views/dashboard/configuration/widget/metric/Standard.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<!-- 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 class="config-panel">
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("unit") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="currentMetric.unit"
|
||||
size="small"
|
||||
placeholder="Please input unit"
|
||||
@change="changeConfigs(index, { unit: currentMetric.unit })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10" v-show="metricType === 'readLabeledMetricsValues'">
|
||||
<span class="label">{{ t("labels") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="currentMetric.label"
|
||||
size="small"
|
||||
placeholder="Please input a name"
|
||||
@change="changeConfigs(index, { label: currentMetric.label })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10" v-show="metricType === 'readLabeledMetricsValues'">
|
||||
<span class="label">{{ t("labelsIndex") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="currentMetric.labelsIndex"
|
||||
size="small"
|
||||
placeholder="auto"
|
||||
@change="
|
||||
changeConfigs(index, { labelsIndex: currentMetric.labelsIndex })
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("aggregation") }}</span>
|
||||
<SelectSingle
|
||||
:value="currentMetric.calculation"
|
||||
:options="CalculationOpts"
|
||||
@change="changeConfigs(index, { calculation: $event })"
|
||||
class="selectors"
|
||||
:clearable="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
v-show="['sortMetrics', 'readSampledRecords'].includes(metricType)"
|
||||
>
|
||||
<span class="label">{{ t("sortOrder") }}</span>
|
||||
<SelectSingle
|
||||
:value="currentMetric.sortOrder || 'DES'"
|
||||
:options="SortOrder"
|
||||
class="selectors"
|
||||
@change="changeConfigs(index, { sortOrder: $event })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { SortOrder, CalculationOpts } from "../../../data";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
currentMetricConfig: {
|
||||
type: Object as PropType<MetricConfigOpt>,
|
||||
default: () => ({ unit: "" }),
|
||||
},
|
||||
index: { type: Number, default: 0 },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(["update"]);
|
||||
const dashboardStore = useDashboardStore();
|
||||
const currentMetric = ref<MetricConfigOpt>(props.currentMetricConfig);
|
||||
const metricType = ref<string>(
|
||||
dashboardStore.selectedGrid.metricTypes[props.index]
|
||||
);
|
||||
|
||||
function changeConfigs(index: number, param: { [key: string]: string }) {
|
||||
const metricConfig = dashboardStore.selectedGrid.metricConfig || [];
|
||||
metricConfig[index] = { ...metricConfig[index], ...param };
|
||||
|
||||
dashboardStore.selectWidget({
|
||||
...dashboardStore.selectedGrid,
|
||||
metricConfig,
|
||||
});
|
||||
emit("update");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.config-panel {
|
||||
padding: 10px 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 150px;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -15px;
|
||||
}
|
||||
|
||||
.selectors {
|
||||
width: 365px;
|
||||
}
|
||||
</style>
|
@ -36,18 +36,17 @@ limitations under the License. -->
|
||||
</div>
|
||||
<div
|
||||
class="body"
|
||||
:style="{ backgroundColor: TextColors[data.graph.backgroundColor] }"
|
||||
:class="data.graph.textAlign === 'center' ? 'center' : ''"
|
||||
:style="{
|
||||
backgroundColor: TextColors[data.graph.backgroundColor],
|
||||
color: TextColors[data.graph.fontColor],
|
||||
fontSize: data.graph.fontSize + 'px',
|
||||
textAlign: data.graph.textAlign,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
:style="{
|
||||
color: TextColors[data.graph.fontColor],
|
||||
fontSize: data.graph.fontSize + 'px',
|
||||
}"
|
||||
>
|
||||
<a :href="data.graph.url" target="_blank">
|
||||
{{ data.graph.content }}
|
||||
</a>
|
||||
</div>
|
||||
<a :href="data.graph.url" target="_blank">
|
||||
{{ data.graph.content }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -75,14 +74,6 @@ function editConfig() {
|
||||
dashboardStore.setConfigPanel(true);
|
||||
dashboardStore.selectWidget(props.data);
|
||||
}
|
||||
function viewText() {
|
||||
const path = props.data.graph.url;
|
||||
console.log(path);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
window.open(path, "_blank");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.topology {
|
||||
@ -97,21 +88,21 @@ function viewText() {
|
||||
|
||||
.header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.body {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 100%;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-box-align: center;
|
||||
box-sizing: border-box;
|
||||
display: -webkit-box;
|
||||
// -webkit-box-align: left;
|
||||
}
|
||||
|
||||
.tools {
|
||||
|
@ -64,8 +64,8 @@ limitations under the License. -->
|
||||
metrics: data.metrics,
|
||||
metricTypes: data.metricTypes,
|
||||
i: data.i,
|
||||
metricConfig: data.metricConfig,
|
||||
}"
|
||||
:standard="data.standard"
|
||||
:needQuery="needQuery"
|
||||
/>
|
||||
</div>
|
||||
@ -128,7 +128,7 @@ export default defineComponent({
|
||||
const d = {
|
||||
metrics: props.data.metrics,
|
||||
metricTypes: props.data.metricTypes,
|
||||
standard: props.data.standard,
|
||||
metricConfig: props.data.metricConfig || [],
|
||||
};
|
||||
state.source = useSourceProcessor(json, d);
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ export enum LegendOpt {
|
||||
}
|
||||
export const DepthList = [1, 2, 3, 4, 5].map((item: number) => ({
|
||||
value: item,
|
||||
label: item,
|
||||
label: String(item),
|
||||
}));
|
||||
export const Status = [
|
||||
{ label: "All", value: "ALL" },
|
||||
@ -264,3 +264,15 @@ export const TextColors: { [key: string]: string } = {
|
||||
black: "#000",
|
||||
orange: "#E6A23C",
|
||||
};
|
||||
|
||||
export const CalculationOpts = [
|
||||
{ label: "Percentage", value: "percentage" },
|
||||
{ label: "Byte to KB", value: "byteToKB" },
|
||||
{ label: "Apdex", value: "apdex" },
|
||||
{
|
||||
label: "Convert milliseconds to YYYY-MM-DD HH:mm:ss",
|
||||
value: "convertMilliseconds",
|
||||
},
|
||||
{ label: "Convert seconds to YYYY-MM-DD HH:mm:ss", value: "convertSeconds" },
|
||||
{ label: "Precision is 2", value: "precision" },
|
||||
];
|
||||
|
@ -26,12 +26,14 @@ limitations under the License. -->
|
||||
? null
|
||||
: singleVal.toFixed(2)
|
||||
}}
|
||||
<span v-show="config.showUint">{{ standard.unit }}</span>
|
||||
<span v-show="config.showUnit">
|
||||
{{ metricConfig[0]?.unit }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, PropType } from "vue";
|
||||
import { CardConfig, StandardConfig } from "@/types/dashboard";
|
||||
import { CardConfig, MetricConfigOpt } from "@/types/dashboard";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
@ -40,14 +42,16 @@ const props = defineProps({
|
||||
default: () => ({}),
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<CardConfig>,
|
||||
default: () => ({ fontSize: 12, showUint: true, textAlign: "center" }),
|
||||
},
|
||||
standard: {
|
||||
type: Object as PropType<StandardConfig>,
|
||||
default: () => ({ unit: "" }),
|
||||
type: Object as PropType<CardConfig & { metricConfig?: MetricConfigOpt[] }>,
|
||||
default: () => ({
|
||||
fontSize: 12,
|
||||
showUnit: true,
|
||||
textAlign: "center",
|
||||
metricConfig: [],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const metricConfig = computed(() => props.config.metricConfig || []);
|
||||
const key = computed(() => Object.keys(props.data)[0]);
|
||||
const singleVal = computed(() => props.data[key.value]);
|
||||
</script>
|
||||
|
@ -44,7 +44,7 @@ limitations under the License. -->
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="(metric, index) in config.metrics"
|
||||
:label="metric"
|
||||
:label="`${metric} ${getUnit(index)}`"
|
||||
:key="metric + index"
|
||||
>
|
||||
<template #default="scope">
|
||||
@ -92,6 +92,7 @@ import Card from "./Card.vue";
|
||||
import { EntityType } from "../data";
|
||||
import router from "@/router";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
@ -104,7 +105,7 @@ const props = defineProps({
|
||||
i: string;
|
||||
metrics: string[];
|
||||
metricTypes: string[];
|
||||
}
|
||||
} & { metricConfig: MetricConfigOpt[] }
|
||||
>,
|
||||
default: () => ({ dashboardName: "", fontSize: 12, i: "" }),
|
||||
},
|
||||
@ -155,7 +156,12 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
|
||||
ElMessage.error(json.errors);
|
||||
return;
|
||||
}
|
||||
endpoints.value = usePodsSource(currentPods, json, props.config);
|
||||
const metricConfig = props.config.metricConfig || [];
|
||||
|
||||
endpoints.value = usePodsSource(currentPods, json, {
|
||||
...props.config,
|
||||
metricConfig: metricConfig,
|
||||
});
|
||||
return;
|
||||
}
|
||||
endpoints.value = currentPods;
|
||||
@ -184,12 +190,21 @@ async function searchList() {
|
||||
const limit = searchText.value ? undefined : total;
|
||||
await queryEndpoints(limit);
|
||||
}
|
||||
function getUnit(index: number) {
|
||||
const u =
|
||||
(props.config.metricConfig &&
|
||||
props.config.metricConfig[index] &&
|
||||
props.config.metricConfig[index].unit) ||
|
||||
"";
|
||||
if (u) {
|
||||
return `(${u})`;
|
||||
}
|
||||
return u;
|
||||
}
|
||||
watch(
|
||||
() => [props.config.metricTypes, props.config.metrics],
|
||||
async () => {
|
||||
if (props.isEdit) {
|
||||
queryEndpointMetrics(endpoints.value);
|
||||
}
|
||||
queryEndpointMetrics(endpoints.value);
|
||||
// emit("changeOpt", false);
|
||||
}
|
||||
);
|
||||
|
@ -44,7 +44,7 @@ limitations under the License. -->
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="(metric, index) in config.metrics"
|
||||
:label="metric"
|
||||
:label="`${metric} ${getUnit(index)}`"
|
||||
:key="metric + index"
|
||||
>
|
||||
<template #default="scope">
|
||||
@ -112,6 +112,7 @@ import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
|
||||
import { EntityType } from "../data";
|
||||
import router from "@/router";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
@ -122,7 +123,7 @@ const props = defineProps({
|
||||
metrics: string[];
|
||||
metricTypes: string[];
|
||||
isEdit: boolean;
|
||||
}
|
||||
} & { metricConfig: MetricConfigOpt[] }
|
||||
>,
|
||||
default: () => ({
|
||||
dashboardName: "",
|
||||
@ -180,7 +181,11 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
|
||||
ElMessage.error(json.errors);
|
||||
return;
|
||||
}
|
||||
instances.value = usePodsSource(currentInstances, json, props.config);
|
||||
const metricConfig = props.config.metricConfig || [];
|
||||
instances.value = usePodsSource(currentInstances, json, {
|
||||
...props.config,
|
||||
metricConfig,
|
||||
});
|
||||
return;
|
||||
}
|
||||
instances.value = currentInstances;
|
||||
@ -214,6 +219,18 @@ function searchList() {
|
||||
instances.value = searchInstances.value.splice(0, pageSize);
|
||||
}
|
||||
|
||||
function getUnit(index: number) {
|
||||
const u =
|
||||
(props.config.metricConfig &&
|
||||
props.config.metricConfig[index] &&
|
||||
props.config.metricConfig[index].unit) ||
|
||||
"";
|
||||
if (u) {
|
||||
return `(${u})`;
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.config.metricTypes, props.config.metrics],
|
||||
() => {
|
||||
|
@ -56,7 +56,7 @@ limitations under the License. -->
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="(metric, index) in config.metrics"
|
||||
:label="metric"
|
||||
:label="`${metric} ${getUnit(index)}`"
|
||||
:key="metric + index"
|
||||
>
|
||||
<template #default="scope">
|
||||
@ -104,6 +104,7 @@ import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
|
||||
import { EntityType } from "../data";
|
||||
import router from "@/router";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
@ -117,7 +118,7 @@ const props = defineProps({
|
||||
metrics: string[];
|
||||
metricTypes: string[];
|
||||
isEdit: boolean;
|
||||
}
|
||||
} & { metricConfig: MetricConfigOpt[] }
|
||||
>,
|
||||
default: () => ({ dashboardName: "", fontSize: 12 }),
|
||||
},
|
||||
@ -214,7 +215,11 @@ async function queryServiceMetrics(currentServices: Service[]) {
|
||||
ElMessage.error(json.errors);
|
||||
return;
|
||||
}
|
||||
services.value = usePodsSource(currentServices, json, props.config);
|
||||
const metricConfig = props.config.metricConfig || [];
|
||||
services.value = usePodsSource(currentServices, json, {
|
||||
...props.config,
|
||||
metricConfig: metricConfig || [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
services.value = currentServices;
|
||||
@ -250,6 +255,17 @@ function searchList() {
|
||||
);
|
||||
services.value = searchServices.splice(0, pageSize);
|
||||
}
|
||||
function getUnit(index: number) {
|
||||
const u =
|
||||
(props.config.metricConfig &&
|
||||
props.config.metricConfig[index] &&
|
||||
props.config.metricConfig[index].unit) ||
|
||||
"";
|
||||
if (u) {
|
||||
return `(${u})`;
|
||||
}
|
||||
return u;
|
||||
}
|
||||
watch(
|
||||
() => [props.config.metricTypes, props.config.metrics],
|
||||
() => {
|
||||
|
@ -90,6 +90,8 @@ import { Option } from "@/types/app";
|
||||
import { Service } from "@/types/selector";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
import { aggregation } from "@/hooks/useProcessor";
|
||||
|
||||
/*global Nullable, defineProps */
|
||||
const props = defineProps({
|
||||
@ -275,13 +277,17 @@ function update() {
|
||||
handleNodeClick: handleNodeClick,
|
||||
tipHtml: (data: Node) => {
|
||||
const nodeMetrics: string[] = settings.value.nodeMetrics || [];
|
||||
const html = nodeMetrics.map((m) => {
|
||||
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
|
||||
const html = nodeMetrics.map((m, index) => {
|
||||
const metric =
|
||||
topologyStore.nodeMetricValue[m].values.filter(
|
||||
topologyStore.nodeMetricValue[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0] || {};
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||
) || {};
|
||||
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
|
||||
const v = aggregation(metric.value, opt);
|
||||
return ` <div class="mb-5"><span class="grey">${
|
||||
opt.label || m
|
||||
}: </span>${v} ${opt.unit || ""}</div>`;
|
||||
});
|
||||
return [
|
||||
` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`,
|
||||
@ -306,24 +312,34 @@ function update() {
|
||||
tipHtml: (data: Call) => {
|
||||
const linkClientMetrics: string[] =
|
||||
settings.value.linkClientMetrics || [];
|
||||
const linkServerMetricConfig: MetricConfigOpt[] =
|
||||
settings.value.linkServerMetricConfig || [];
|
||||
const linkClientMetricConfig: MetricConfigOpt[] =
|
||||
settings.value.linkClientMetricConfig || [];
|
||||
const linkServerMetrics: string[] =
|
||||
settings.value.linkServerMetrics || [];
|
||||
const htmlServer = linkServerMetrics.map((m) => {
|
||||
const metric = topologyStore.linkServerMetrics[m].values.filter(
|
||||
const htmlServer = linkServerMetrics.map((m, index) => {
|
||||
const metric = topologyStore.linkServerMetrics[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0];
|
||||
);
|
||||
if (metric) {
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
|
||||
const v = aggregation(metric.value, opt);
|
||||
return ` <div class="mb-5"><span class="grey">${
|
||||
opt.label || m
|
||||
}: </span>${v} ${opt.unit || ""}</div>`;
|
||||
}
|
||||
});
|
||||
const htmlClient = linkClientMetrics.map((m) => {
|
||||
const metric = topologyStore.linkClientMetrics[m].values.filter(
|
||||
const htmlClient = linkClientMetrics.map((m: string, index: number) => {
|
||||
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
|
||||
const metric = topologyStore.linkClientMetrics[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0];
|
||||
);
|
||||
if (metric) {
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||
const v = aggregation(metric.value, opt);
|
||||
return ` <div class="mb-5"><span class="grey">${
|
||||
opt.label || m
|
||||
}: </span>${v} ${opt.unit || ""}</div>`;
|
||||
}
|
||||
});
|
||||
const html = [
|
||||
|
170
src/views/dashboard/related/topology/components/Metrics.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<!-- 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 class="config-panel">
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("metrics") }}</span>
|
||||
<SelectSingle
|
||||
:value="currentMetric"
|
||||
:options="metrics"
|
||||
@change="changeMetric"
|
||||
class="selectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("unit") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="currentConfig.unit"
|
||||
size="small"
|
||||
placeholder="Please input unit"
|
||||
@change="changeConfigs({ unit: currentConfig.unit })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("labels") }}</span>
|
||||
<el-input
|
||||
class="input"
|
||||
v-model="currentConfig.label"
|
||||
size="small"
|
||||
placeholder="Please input a label"
|
||||
@change="changeConfigs({ label: currentConfig.label })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("aggregation") }}</span>
|
||||
<SelectSingle
|
||||
:value="currentConfig.calculation"
|
||||
:options="CalculationOpts"
|
||||
@change="changeConfigs({ calculation: $event })"
|
||||
class="selectors"
|
||||
:clearable="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { CalculationOpts } from "../../../data";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
import { 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: () => [] },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(["update"]);
|
||||
const dashboardStore = useDashboardStore();
|
||||
const m = props.metrics.map((d: string) => {
|
||||
return { label: d, value: d };
|
||||
});
|
||||
const metrics = ref<Option[]>(m.length ? m : [{ label: "", value: "" }]);
|
||||
const currentMetric = ref<string>(metrics.value[0].value);
|
||||
const currentConfig = ref<{ unit: string; calculation: string; label: string }>(
|
||||
{
|
||||
unit: "",
|
||||
calculation: "",
|
||||
label: "",
|
||||
}
|
||||
);
|
||||
const currentIndex = ref<number>(0);
|
||||
const getMetricConfig = computed(() => {
|
||||
let config = [];
|
||||
switch (props.type) {
|
||||
case "linkServerMetricConfig":
|
||||
config = dashboardStore.selectedGrid.linkServerMetricConfig;
|
||||
break;
|
||||
case "linkClientMetricConfig":
|
||||
config = dashboardStore.selectedGrid.linkClientMetricConfig;
|
||||
break;
|
||||
case "nodeMetricConfig":
|
||||
config = dashboardStore.selectedGrid.nodeMetricConfig;
|
||||
break;
|
||||
}
|
||||
return config || [];
|
||||
});
|
||||
|
||||
function changeConfigs(param: { [key: string]: string }) {
|
||||
const metricConfig = getMetricConfig.value;
|
||||
metricConfig[currentIndex.value] = {
|
||||
...metricConfig[currentIndex.value],
|
||||
...param,
|
||||
};
|
||||
emit("update", { [props.type]: metricConfig });
|
||||
}
|
||||
function changeMetric(val: string) {
|
||||
currentMetric.value = val;
|
||||
const index = metrics.value.findIndex((d: Option) => d.value === val);
|
||||
currentIndex.value = index || 0;
|
||||
const config = getMetricConfig.value || [];
|
||||
|
||||
currentConfig.value = {
|
||||
unit: "",
|
||||
label: "",
|
||||
calculation: "",
|
||||
...config[index],
|
||||
};
|
||||
}
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
const m = props.metrics.map((d: string) => {
|
||||
return { label: d, value: d };
|
||||
});
|
||||
metrics.value = m.length ? m : [{ label: "", value: "" }];
|
||||
currentMetric.value = metrics.value[0].value;
|
||||
const config = getMetricConfig.value || [];
|
||||
currentIndex.value = 0;
|
||||
currentConfig.value = {
|
||||
unit: "",
|
||||
label: "",
|
||||
calculation: "",
|
||||
...config[0],
|
||||
};
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.config-panel {
|
||||
padding: 10px 5px;
|
||||
position: relative;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 150px;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -15px;
|
||||
}
|
||||
|
||||
.selectors {
|
||||
width: 365px;
|
||||
}
|
||||
</style>
|
@ -54,7 +54,7 @@ limitations under the License. -->
|
||||
element-loading-background="rgba(0, 0, 0, 0)"
|
||||
@click="handleClick"
|
||||
>
|
||||
<Sankey @click="selectNodeLink" />
|
||||
<Sankey @click="selectNodeLink" :settings="settings" />
|
||||
</div>
|
||||
<div
|
||||
class="operations-list"
|
||||
|
@ -17,11 +17,19 @@ limitations under the License. -->
|
||||
<Graph :option="option" @select="clickChart" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { computed, PropType } from "vue";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
import { Node, Call } from "@/types/topology";
|
||||
import { MetricConfigOpt } from "@/types/dashboard";
|
||||
import { aggregation } from "@/hooks/useProcessor";
|
||||
|
||||
/*global defineEmits */
|
||||
/*global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
settings: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["click"]);
|
||||
const topologyStore = useTopologyStore();
|
||||
const option = computed(() => getOption());
|
||||
@ -77,23 +85,34 @@ function getOption() {
|
||||
function linkTooltip(data: Call) {
|
||||
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
|
||||
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
|
||||
const htmlServer = serverMetrics.map((m) => {
|
||||
const metric = topologyStore.linkServerMetrics[m].values.filter(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0];
|
||||
const linkServerMetricConfig: MetricConfigOpt[] =
|
||||
props.settings.linkServerMetricConfig || [];
|
||||
const linkClientMetricConfig: MetricConfigOpt[] =
|
||||
props.settings.linkClientMetricConfig || [];
|
||||
|
||||
const htmlServer = serverMetrics.map((m, index) => {
|
||||
const metric =
|
||||
topologyStore.linkServerMetrics[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
) || {};
|
||||
if (metric) {
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div><span>${m}: </span>${val}</div>`;
|
||||
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
|
||||
const v = aggregation(metric.value, opt);
|
||||
return ` <div class="mb-5"><span class="grey">${
|
||||
opt.label || m
|
||||
}: </span>${v} ${opt.unit || ""}</div>`;
|
||||
}
|
||||
});
|
||||
const htmlClient = clientMetrics.map((m) => {
|
||||
const metric = topologyStore.linkClientMetrics[m].values.filter(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0];
|
||||
if (metric) {
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div><span>${m}: </span>${val}</div>`;
|
||||
}
|
||||
const htmlClient = clientMetrics.map((m, index) => {
|
||||
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
|
||||
const metric =
|
||||
topologyStore.linkClientMetrics[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
) || {};
|
||||
const v = aggregation(metric.value, opt);
|
||||
return ` <div class="mb-5"><span class="grey">${
|
||||
opt.label || m
|
||||
}: </span>${v} ${opt.unit || ""}</div>`;
|
||||
});
|
||||
const html = [
|
||||
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
|
||||
@ -106,13 +125,17 @@ function linkTooltip(data: Call) {
|
||||
|
||||
function nodeTooltip(data: Node) {
|
||||
const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetricValue);
|
||||
const html = nodeMetrics.map((m) => {
|
||||
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
|
||||
const html = nodeMetrics.map((m, index) => {
|
||||
const metric =
|
||||
topologyStore.nodeMetricValue[m].values.filter(
|
||||
topologyStore.nodeMetricValue[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0] || {};
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div><span>${m}: </span>${val}</div>`;
|
||||
) || {};
|
||||
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
|
||||
const v = aggregation(metric.value, opt);
|
||||
return ` <div class="mb-5"><span class="grey">${
|
||||
opt.label || m
|
||||
}: </span>${v} ${opt.unit || ""}</div>`;
|
||||
});
|
||||
return [` <div><span>name: </span>${data.serviceName}</div>`, ...html].join(
|
||||
" "
|
||||
|
@ -25,7 +25,27 @@ limitations under the License. -->
|
||||
class="inputs"
|
||||
:clearable="true"
|
||||
/>
|
||||
<div class="label">{{ t("linkServerMetrics") }}</div>
|
||||
<div class="label">
|
||||
<span>{{ t("linkServerMetrics") }}</span>
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
effect="dark"
|
||||
v-if="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"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<Selector
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
@ -33,11 +53,29 @@ limitations under the License. -->
|
||||
:options="states.linkMetricList"
|
||||
size="small"
|
||||
placeholder="Select metrics"
|
||||
@change="changeLinkServerMetrics"
|
||||
@change="updateLinkServerMetrics"
|
||||
/>
|
||||
<span v-show="dashboardStore.entity !== EntityType[2].value">
|
||||
<div class="label">
|
||||
{{ t("linkClientMetrics") }}
|
||||
<span>{{ t("linkClientMetrics") }}</span>
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
effect="dark"
|
||||
v-if="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"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<Selector
|
||||
class="inputs"
|
||||
@ -46,7 +84,7 @@ limitations under the License. -->
|
||||
:options="states.linkMetricList"
|
||||
size="small"
|
||||
placeholder="Select metrics"
|
||||
@change="changeLinkClientMetrics"
|
||||
@change="updateLinkClientMetrics"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@ -100,7 +138,27 @@ limitations under the License. -->
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="label">{{ t("nodeMetrics") }}</div>
|
||||
<div class="label">
|
||||
<span>{{ t("nodeMetrics") }}</span>
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
effect="dark"
|
||||
v-if="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"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<Selector
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
@ -108,7 +166,7 @@ limitations under the License. -->
|
||||
:options="states.nodeMetricList"
|
||||
size="small"
|
||||
placeholder="Select metrics"
|
||||
@change="changeNodeMetrics"
|
||||
@change="updateNodeMetrics"
|
||||
/>
|
||||
</div>
|
||||
<div class="legend-settings" v-show="isService">
|
||||
@ -157,15 +215,6 @@ limitations under the License. -->
|
||||
</span>
|
||||
<div v-show="index !== legend.metric.length - 1">&&</div>
|
||||
</div>
|
||||
<!-- <div class="label">{{ t("conditions") }}</div>
|
||||
<Selector
|
||||
class="inputs"
|
||||
:value="legend.condition"
|
||||
:options="LegendConditions"
|
||||
size="small"
|
||||
placeholder="Select a condition"
|
||||
@change="changeCondition"
|
||||
/> -->
|
||||
<el-button
|
||||
@click="setLegend"
|
||||
class="legend-btn"
|
||||
@ -177,7 +226,7 @@ limitations under the License. -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from "vue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
@ -186,8 +235,9 @@ import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
|
||||
import { Option } from "@/types/app";
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
|
||||
import { Node } from "@/types/topology";
|
||||
import { DashboardItem } from "@/types/dashboard";
|
||||
import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||
import { EntityType, LegendOpt, MetricsType } from "../../../data";
|
||||
import Metrics from "./Metrics.vue";
|
||||
|
||||
/*global defineEmits */
|
||||
const emit = defineEmits(["update", "updateNodes"]);
|
||||
@ -238,6 +288,7 @@ const legend = reactive<{
|
||||
}>({
|
||||
metric: l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
||||
});
|
||||
const configType = ref<string>("");
|
||||
|
||||
getMetricList();
|
||||
async function getMetricList() {
|
||||
@ -347,11 +398,12 @@ function deleteItem(index: number) {
|
||||
items.splice(index, 1);
|
||||
updateSettings();
|
||||
}
|
||||
function updateSettings() {
|
||||
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
|
||||
const metrics = legend.metric.filter(
|
||||
(d: any) => d.name && d.value && d.condition
|
||||
);
|
||||
const param = {
|
||||
...dashboardStore.selectedGrid,
|
||||
linkDashboard: states.linkDashboard,
|
||||
nodeDashboard: isService
|
||||
? items.filter((d: { scope: string; dashboard: string }) => d.dashboard)
|
||||
@ -360,32 +412,76 @@ function updateSettings() {
|
||||
linkClientMetrics: states.linkClientMetrics,
|
||||
nodeMetrics: states.nodeMetrics,
|
||||
legend: metrics,
|
||||
...metricConfig,
|
||||
};
|
||||
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, ...param });
|
||||
dashboardStore.setConfigs({ ...dashboardStore.selectedGrid, ...param });
|
||||
dashboardStore.selectWidget(param);
|
||||
dashboardStore.setConfigs(param);
|
||||
emit("update", param);
|
||||
}
|
||||
async function changeLinkServerMetrics(options: Option[] | any) {
|
||||
states.linkServerMetrics = options.map((d: Option) => d.value);
|
||||
updateSettings();
|
||||
function updateLinkServerMetrics(options: Option[] | any) {
|
||||
const opt = options.map((d: Option) => d.value);
|
||||
const index = states.linkServerMetrics.findIndex(
|
||||
(d: any) => !opt.includes(d)
|
||||
);
|
||||
states.linkServerMetrics = opt;
|
||||
if (index < 0) {
|
||||
changeLinkServerMetrics();
|
||||
return;
|
||||
}
|
||||
const origin = dashboardStore.selectedGrid.linkServerMetricConfig || [];
|
||||
const config = origin.length === 1 ? [] : origin.splice(index, 1);
|
||||
changeLinkServerMetrics({ linkServerMetricConfig: config });
|
||||
}
|
||||
async function changeLinkServerMetrics(config?: {
|
||||
[key: string]: MetricConfigOpt[];
|
||||
}) {
|
||||
updateSettings(config);
|
||||
if (!states.linkServerMetrics.length) {
|
||||
topologyStore.setLinkServerMetrics({});
|
||||
return;
|
||||
}
|
||||
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
|
||||
}
|
||||
async function changeLinkClientMetrics(options: Option[] | any) {
|
||||
states.linkClientMetrics = options.map((d: Option) => d.value);
|
||||
updateSettings();
|
||||
function updateLinkClientMetrics(options: Option[] | any) {
|
||||
const opt = options.map((d: Option) => d.value);
|
||||
const index = states.linkClientMetrics.findIndex(
|
||||
(d: any) => !opt.includes(d)
|
||||
);
|
||||
states.linkClientMetrics = opt;
|
||||
if (index < 0) {
|
||||
changeLinkClientMetrics();
|
||||
return;
|
||||
}
|
||||
const origin = dashboardStore.selectedGrid.linkClientMetricConfig || [];
|
||||
const config = origin.length === 1 ? [] : origin.splice(index, 1);
|
||||
changeLinkClientMetrics({ linkClientMetricConfig: config });
|
||||
}
|
||||
async function changeLinkClientMetrics(config?: {
|
||||
[key: string]: MetricConfigOpt[];
|
||||
}) {
|
||||
updateSettings(config);
|
||||
if (!states.linkClientMetrics.length) {
|
||||
topologyStore.setLinkClientMetrics({});
|
||||
return;
|
||||
}
|
||||
topologyStore.getLinkClientMetrics(states.linkClientMetrics);
|
||||
}
|
||||
async function changeNodeMetrics(options: Option[] | any) {
|
||||
states.nodeMetrics = options.map((d: Option) => d.value);
|
||||
updateSettings();
|
||||
function updateNodeMetrics(options: Option[] | any) {
|
||||
const opt = options.map((d: Option) => d.value);
|
||||
const index = states.nodeMetrics.findIndex((d: any) => !opt.includes(d));
|
||||
states.nodeMetrics = opt;
|
||||
if (index < 0) {
|
||||
changeNodeMetrics();
|
||||
return;
|
||||
}
|
||||
const origin = dashboardStore.selectedGrid.nodeMetricConfig || [];
|
||||
const config = origin.length === 1 ? [] : origin.splice(index, 1);
|
||||
changeNodeMetrics({ nodeMetricConfig: config });
|
||||
}
|
||||
async function changeNodeMetrics(config?: {
|
||||
[key: string]: MetricConfigOpt[];
|
||||
}) {
|
||||
updateSettings(config);
|
||||
if (!states.nodeMetrics.length) {
|
||||
topologyStore.setNodeMetricValue({});
|
||||
return;
|
||||
@ -402,6 +498,9 @@ function deleteMetric(index: number) {
|
||||
function addMetric() {
|
||||
legend.metric.push({ name: "", condition: "", value: "" });
|
||||
}
|
||||
function setConfigType(type: string) {
|
||||
configType.value = type;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.link-settings {
|
||||
|