feat: set topology legend

This commit is contained in:
Qiuxia Fan 2022-02-17 20:41:48 +08:00
parent 83805b614f
commit 6bccb79eaf
8 changed files with 222 additions and 44 deletions

View File

@ -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",

View File

@ -83,6 +83,8 @@ const msg = {
endpointDashboard: "拓节点端点的实例的仪表板名称",
callSettings: "拓扑线设置",
nodeSettings: "拓扑点设置",
conditions: "条件",
legendSettings: "图例设置",
hourTip: "选择小时",
minuteTip: "选择分钟",
secondTip: "选择秒数",

View File

@ -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 };

View File

@ -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",
}

View File

@ -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;

View File

@ -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" />

View File

@ -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>

View File

@ -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)