mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 14:45:25 +00:00
feat: set topology legend
This commit is contained in:
parent
83805b614f
commit
6bccb79eaf
@ -84,6 +84,8 @@ const msg = {
|
||||
endpointDashboard: "Dashboard name related with endpoints",
|
||||
callSettings: "Call settings",
|
||||
nodeSettings: "Node Settings",
|
||||
conditions: "Conditions",
|
||||
legendSettings: "Legend Settings",
|
||||
hourTip: "Select Hour",
|
||||
minuteTip: "Select Minute",
|
||||
secondTip: "Select Second",
|
||||
|
@ -83,6 +83,8 @@ const msg = {
|
||||
endpointDashboard: "拓节点端点的实例的仪表板名称",
|
||||
callSettings: "拓扑线设置",
|
||||
nodeSettings: "拓扑点设置",
|
||||
conditions: "条件",
|
||||
legendSettings: "图例设置",
|
||||
hourTip: "选择小时",
|
||||
minuteTip: "选择分钟",
|
||||
secondTip: "选择秒数",
|
||||
|
@ -344,6 +344,29 @@ export const topologyStore = defineStore({
|
||||
this.setNodeMetrics(res.data.data);
|
||||
return res.data;
|
||||
},
|
||||
async getLegendMetrics(param: {
|
||||
queryStr: string;
|
||||
conditions: { [key: string]: unknown };
|
||||
}) {
|
||||
const res: AxiosResponse = await query(param);
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
const data = res.data.data;
|
||||
const metrics = Object.keys(data);
|
||||
this.nodes = this.nodes.map((d: Node | any) => {
|
||||
for (const m of metrics) {
|
||||
for (const val of data[m].values) {
|
||||
if (d.id === val.id) {
|
||||
d[m] = val.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return d;
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
async getCallServerMetrics(param: {
|
||||
queryStr: string;
|
||||
conditions: { [key: string]: unknown };
|
||||
|
@ -177,3 +177,16 @@ export const ScopeType = [
|
||||
{ value: "Endpoint", label: "Endpoint", key: 3 },
|
||||
{ value: "ServiceInstance", label: "Service Instance", key: 3 },
|
||||
];
|
||||
export const LegendConditions = [
|
||||
{ label: "&&", value: "and" },
|
||||
{ label: "||", value: "or" },
|
||||
];
|
||||
export const MetricConditions = [
|
||||
{ label: ">", value: ">" },
|
||||
{ label: "<", value: "<" },
|
||||
];
|
||||
export enum LegendOpt {
|
||||
NAME = "name",
|
||||
VALUE = "value",
|
||||
CONDITION = "condition",
|
||||
}
|
||||
|
@ -20,18 +20,18 @@ limitations under the License. -->
|
||||
:style="`height: ${height}px`"
|
||||
>
|
||||
<div class="setting" v-show="showSetting">
|
||||
<Settings @update="updateSettings" />
|
||||
<Settings @update="updateSettings" @updateNodes="freshNodes" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<span class="switch-icon ml-5" title="Settings">
|
||||
<Icon @click="setConfig" size="middle" iconName="settings" />
|
||||
<span class="switch-icon ml-5" title="Settings" @click="setConfig">
|
||||
<Icon size="middle" iconName="settings" />
|
||||
</span>
|
||||
<span class="switch-icon ml-5" title="Back to overview topology">
|
||||
<Icon
|
||||
<span
|
||||
class="switch-icon ml-5"
|
||||
title="Back to overview topology"
|
||||
@click="backToTopology"
|
||||
size="middle"
|
||||
iconName="keyboard_backspace"
|
||||
/>
|
||||
>
|
||||
<Icon size="middle" iconName="keyboard_backspace" />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@ -239,7 +239,10 @@ function update() {
|
||||
topologyStore.nodeMetrics[m].values.filter(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0] || {};
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${metric.value}</div>`;
|
||||
const val = m.includes("_sla")
|
||||
? metric.value / 100
|
||||
: metric.value.value;
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||
});
|
||||
return [
|
||||
` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`,
|
||||
@ -247,7 +250,8 @@ function update() {
|
||||
].join(" ");
|
||||
},
|
||||
},
|
||||
tip.value
|
||||
tip.value,
|
||||
settings.value.legend
|
||||
).merge(node.value);
|
||||
// line element
|
||||
link.value = link.value.data(topologyStore.calls, (d: Call) => d.id);
|
||||
@ -270,7 +274,8 @@ function update() {
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0];
|
||||
if (metric) {
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${metric.value}</div>`;
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||
}
|
||||
});
|
||||
const htmlClient = linkClientMetrics.map((m) => {
|
||||
@ -278,7 +283,8 @@ function update() {
|
||||
(val: { id: string; value: unknown }) => val.id === data.id
|
||||
)[0];
|
||||
if (metric) {
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${metric.value}</div>`;
|
||||
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||
}
|
||||
});
|
||||
const html = [
|
||||
@ -433,6 +439,11 @@ function updateSettings(config: any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
async function freshNodes() {
|
||||
svg.value.selectAll(".topo-svg-graph").remove();
|
||||
await init();
|
||||
update();
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
@ -445,7 +456,7 @@ onBeforeUnmount(() => {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
right: 0;
|
||||
width: 360px;
|
||||
width: 380px;
|
||||
height: 700px;
|
||||
background-color: #2b3037;
|
||||
overflow: auto;
|
||||
|
@ -24,15 +24,15 @@ limitations under the License. -->
|
||||
@change="changeDepth"
|
||||
/>
|
||||
</span>
|
||||
<span class="switch-icon ml-5" title="Settings">
|
||||
<Icon @click="setConfig" size="middle" iconName="settings" />
|
||||
<span class="switch-icon ml-5" title="Settings" @click="setConfig">
|
||||
<Icon size="middle" iconName="settings" />
|
||||
</span>
|
||||
<span class="switch-icon ml-5" title="Back to overview topology">
|
||||
<Icon
|
||||
<span
|
||||
class="switch-icon ml-5"
|
||||
title="Back to overview topology"
|
||||
@click="backToTopology"
|
||||
size="middle"
|
||||
iconName="keyboard_backspace"
|
||||
/>
|
||||
>
|
||||
<Icon size="middle" iconName="keyboard_backspace" />
|
||||
</span>
|
||||
<div class="settings" v-show="showSettings">
|
||||
<Settings @update="updateConfig" />
|
||||
|
@ -58,30 +58,30 @@ limitations under the License. -->
|
||||
size="small"
|
||||
placeholder="Select a scope"
|
||||
@change="changeScope(index, $event)"
|
||||
class="item"
|
||||
class="item mr-5"
|
||||
/>
|
||||
<el-input
|
||||
v-model="item.dashboard"
|
||||
placeholder="Please input a dashboard name for nodes"
|
||||
@change="updateNodeDashboards(index, $event)"
|
||||
size="small"
|
||||
class="item"
|
||||
class="item mr-5"
|
||||
/>
|
||||
<span>
|
||||
<Icon
|
||||
class="cp mr-5"
|
||||
v-show="index === items.length - 1 && items.length < 5"
|
||||
iconName="add_circle_outlinecontrol_point"
|
||||
size="middle"
|
||||
@click="addItem"
|
||||
/>
|
||||
<Icon
|
||||
class="cp"
|
||||
v-show="items.length > 1"
|
||||
iconName="remove_circle_outline"
|
||||
size="middle"
|
||||
@click="deleteItem(index)"
|
||||
/>
|
||||
<Icon
|
||||
class="cp"
|
||||
v-show="index === items.length - 1 && items.length < 5"
|
||||
iconName="add_circle_outlinecontrol_point"
|
||||
size="middle"
|
||||
@click="addItem"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="label">{{ t("nodeMetrics") }}</div>
|
||||
@ -95,21 +95,94 @@ limitations under the License. -->
|
||||
@change="changeNodeMetrics"
|
||||
/>
|
||||
</div>
|
||||
<div class="legend-settings">
|
||||
<h5 class="title">{{ t("legendSettings") }}</h5>
|
||||
<div class="label">{{ t("metrics") }}</div>
|
||||
<Selector
|
||||
class="item mr-5"
|
||||
:value="legend.metric.name"
|
||||
:options="states.nodeMetricList"
|
||||
size="small"
|
||||
placeholder="Select a metric"
|
||||
@change="changeLegend(LegendOpt.NAME, $event)"
|
||||
/>
|
||||
<Selector
|
||||
class="input-small mr-5"
|
||||
:value="legend.metric.condition"
|
||||
:options="MetricConditions"
|
||||
size="small"
|
||||
placeholder="Select a condition"
|
||||
@change="changeLegend(LegendOpt.CONDITION, $event)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="legend.metric.value"
|
||||
placeholder="Please input a value"
|
||||
@change="changeLegend(LegendOpt.VALUE, $event)"
|
||||
size="small"
|
||||
class="item"
|
||||
/>
|
||||
<!-- <div class="label">{{ t("conditions") }}</div>
|
||||
<Selector
|
||||
class="inputs"
|
||||
:value="legend.condition"
|
||||
:options="LegendConditions"
|
||||
size="small"
|
||||
placeholder="Select a condition"
|
||||
@change="changeCondition"
|
||||
/> -->
|
||||
<!-- <div class="label">{{ t("metrics") }}</div>
|
||||
<Selector
|
||||
class="item mr-5"
|
||||
:value="legend.secondMetric.name"
|
||||
:options="states.nodeMetricList"
|
||||
size="small"
|
||||
placeholder="Select a metric"
|
||||
@change="changeLegendMetric(LegendOpt.NAME, $event)"
|
||||
/>
|
||||
<Selector
|
||||
class="input-small mr-5"
|
||||
:value="legend.secondMetric.value"
|
||||
:options="states.nodeMetricList"
|
||||
size="small"
|
||||
placeholder="Select a metric"
|
||||
@change="changeLegendMetric(LegendOpt.CONDITION, $event)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="legend.secondMetric.condidtion"
|
||||
placeholder="Please input a value"
|
||||
@change="changeLegendMetric(LegendOpt.VALUE, $event)"
|
||||
size="small"
|
||||
class="item"
|
||||
/> -->
|
||||
<el-button
|
||||
@click="setLegend"
|
||||
class="legend-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
set legend
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import { reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { MetricCatalog, ScopeType } from "../../../data";
|
||||
import {
|
||||
MetricCatalog,
|
||||
ScopeType,
|
||||
MetricConditions,
|
||||
LegendConditions,
|
||||
} from "../../../data";
|
||||
import { Option } from "@/types/app";
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
|
||||
import { Node, Call } from "@/types/topology";
|
||||
import { EntityType } from "../../../data";
|
||||
import { EntityType, LegendOpt } from "../../../data";
|
||||
|
||||
/*global defineEmits */
|
||||
const emit = defineEmits(["update"]);
|
||||
const emit = defineEmits(["update", "updateNodes"]);
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const topologyStore = useTopologyStore();
|
||||
@ -121,7 +194,10 @@ const items = reactive<
|
||||
>([{ scope: "", dashboard: "" }]);
|
||||
const states = reactive<{
|
||||
linkDashboard: string;
|
||||
nodeDashboard: string;
|
||||
nodeDashboard: {
|
||||
scope: string;
|
||||
dashboard: string;
|
||||
}[];
|
||||
linkServerMetrics: string[];
|
||||
linkClientMetrics: string[];
|
||||
nodeMetrics: string[];
|
||||
@ -129,13 +205,18 @@ const states = reactive<{
|
||||
linkMetricList: Option[];
|
||||
}>({
|
||||
linkDashboard: "",
|
||||
nodeDashboard: "",
|
||||
nodeDashboard: [],
|
||||
linkServerMetrics: [],
|
||||
linkClientMetrics: [],
|
||||
nodeMetrics: [],
|
||||
nodeMetricList: [],
|
||||
linkMetricList: [],
|
||||
});
|
||||
const legend = reactive<{
|
||||
metric: any;
|
||||
condition: string;
|
||||
secondMetric: any;
|
||||
}>({ metric: {}, condition: "", secondMetric: {} });
|
||||
|
||||
getMetricList();
|
||||
async function getMetricList() {
|
||||
@ -159,6 +240,26 @@ async function getMetricList() {
|
||||
e + "Relation" === (MetricCatalog as any)[d.catalog]
|
||||
);
|
||||
}
|
||||
async function setLegend() {
|
||||
updateSettings();
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
const param = await useQueryTopologyMetrics([legend.metric.name], ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
emit("updateNodes");
|
||||
}
|
||||
function changeLegend(type: string, opt: any) {
|
||||
legend.metric[type] = opt[0].value || opt;
|
||||
}
|
||||
function changeCondition(opt: Option[]) {
|
||||
legend.condition = opt[0].value;
|
||||
}
|
||||
function changeLegendMetric(type: string, opt: any) {
|
||||
legend.secondMetric[type] = opt[0].value || opt;
|
||||
}
|
||||
function changeScope(index: number, opt: Option[]) {
|
||||
items[index].scope = opt[0].value;
|
||||
items[index].dashboard = "";
|
||||
@ -183,6 +284,7 @@ function updateSettings() {
|
||||
linkServerMetrics: states.linkServerMetrics,
|
||||
linkClientMetrics: states.linkClientMetrics,
|
||||
nodeMetrics: states.nodeMetrics,
|
||||
legend,
|
||||
});
|
||||
}
|
||||
async function changeLinkServerMetrics(options: Option[]) {
|
||||
@ -242,12 +344,16 @@ async function changeNodeMetrics(options: Option[]) {
|
||||
|
||||
.inputs {
|
||||
margin-top: 8px;
|
||||
width: 330px;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 137px;
|
||||
margin: 5px 5px 0 0;
|
||||
width: 140px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.input-small {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@ -258,4 +364,9 @@ async function changeNodeMetrics(options: Option[]) {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.legend-btn {
|
||||
margin-top: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@ -18,7 +18,7 @@ import icons from "@/assets/img/icons";
|
||||
import { Node } from "@/types/topology";
|
||||
|
||||
icons["KAFKA-CONSUMER"] = icons.KAFKA;
|
||||
export default (d3: any, graph: any, funcs: any, tip: any) => {
|
||||
export default (d3: any, graph: any, funcs: any, tip: any, legend: any) => {
|
||||
const nodeEnter = graph
|
||||
.append("g")
|
||||
.call(
|
||||
@ -46,9 +46,25 @@ export default (d3: any, graph: any, funcs: any, tip: any) => {
|
||||
.attr("x", 2)
|
||||
.attr("y", 10)
|
||||
.attr("style", "cursor: move;")
|
||||
.attr("xlink:href", (d: { isReal: number; sla: number; cpm: number }) =>
|
||||
d.sla < 95 && d.isReal && d.cpm > 1 ? icons.CUBEERROR : icons.CUBE
|
||||
);
|
||||
.attr("xlink:href", (d: { [key: string]: number }) => {
|
||||
if (!legend) {
|
||||
return icons.CUBE;
|
||||
}
|
||||
const val = legend.metric.name.includes("_sla")
|
||||
? d[legend.metric.name] / 100
|
||||
: d[legend.metric.name];
|
||||
if (legend.metric.condition === "<") {
|
||||
return val < Number(legend.metric.value) && d.isReal
|
||||
? icons.CUBEERROR
|
||||
: icons.CUBE;
|
||||
}
|
||||
if (legend.metric.condition === ">") {
|
||||
return val > Number(legend.metric.value) && d.isReal
|
||||
? icons.CUBEERROR
|
||||
: icons.CUBE;
|
||||
}
|
||||
return icons.CUBE;
|
||||
});
|
||||
nodeEnter
|
||||
.append("image")
|
||||
.attr("width", 32)
|
||||
|
Loading…
Reference in New Issue
Block a user