feat: add operations for topology nodes

This commit is contained in:
Qiuxia Fan 2022-02-14 14:00:16 +08:00
parent b85313d231
commit 9f77830a1a
5 changed files with 56 additions and 102 deletions

View File

@ -25,6 +25,7 @@ import { routesLog } from "./log";
import { routesEvent } from "./event"; import { routesEvent } from "./event";
import { routesAlert } from "./alert"; import { routesAlert } from "./alert";
import { routesSetting } from "./setting"; import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm";
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
...routesGen, ...routesGen,
@ -36,6 +37,7 @@ const routes: Array<RouteRecordRaw> = [
...routesEvent, ...routesEvent,
...routesAlert, ...routesAlert,
...routesSetting, ...routesSetting,
...routesAlarm,
]; ];
const router = createRouter({ const router = createRouter({

View File

@ -26,12 +26,24 @@ limitations under the License. -->
@click="setConfig" @click="setConfig"
class="switch-icon" class="switch-icon"
size="middle" size="middle"
iconName="format_indent_decrease" iconName="settings"
/> />
<div
class="operations-list"
v-if="topologyStore.node && topologyStore.node.isReal"
:style="{
top: operationsPos.y + 'px',
left: operationsPos.x + 'px',
}"
>
<span v-for="(item, index) of items" :key="index" @click="item.func">
{{ item.title }}
</span>
</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, watch } from "vue"; import { ref, onMounted, onBeforeUnmount, watch, reactive } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import * as d3 from "d3"; import * as d3 from "d3";
import d3tip from "d3-tip"; import d3tip from "d3-tip";
@ -39,7 +51,6 @@ import zoom from "./utils/zoom";
import { simulationInit, simulationSkip } from "./utils/simulation"; import { simulationInit, simulationSkip } from "./utils/simulation";
import nodeElement from "./utils/nodeElement"; import nodeElement from "./utils/nodeElement";
import { linkElement, anchorElement, arrowMarker } from "./utils/linkElement"; import { linkElement, anchorElement, arrowMarker } from "./utils/linkElement";
import tool from "./utils/tool";
import topoLegend from "./utils/legend"; import topoLegend from "./utils/legend";
import { Node, Call } from "@/types/topology"; import { Node, Call } from "@/types/topology";
import { useTopologyStore } from "@/store/modules/topology"; import { useTopologyStore } from "@/store/modules/topology";
@ -65,10 +76,16 @@ const node = ref<any>(null);
const link = ref<any>(null); const link = ref<any>(null);
const anchor = ref<any>(null); const anchor = ref<any>(null);
const arrow = ref<any>(null); const arrow = ref<any>(null);
const tools = ref<any>(null);
const legend = ref<any>(null); const legend = ref<any>(null);
const showSetting = ref<boolean>(false); const showSetting = ref<boolean>(false);
const settings = ref<any>({}); const settings = ref<any>({});
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const items = [
{ title: "Endpoint", func: handleGoEndpoint },
{ title: "Instance", func: handleGoInstance },
{ title: "Dashboard", func: handleGoDashboard },
{ title: "Alarm", func: handleGoAlarm },
];
onMounted(async () => { onMounted(async () => {
loading.value = true; loading.value = true;
@ -98,21 +115,14 @@ onMounted(async () => {
anchor.value = graph.value.append("g").selectAll(".topo-line-anchor"); anchor.value = graph.value.append("g").selectAll(".topo-line-anchor");
arrow.value = graph.value.append("g").selectAll(".topo-line-arrow"); arrow.value = graph.value.append("g").selectAll(".topo-line-arrow");
svg.value.call(zoom(d3, graph.value)); svg.value.call(zoom(d3, graph.value));
tools.value = tool(graph.value, tip.value, [
{ icon: "API", title: "Endpoint", func: handleGoEndpoint },
{ icon: "INSTANCE", title: "Instance", func: handleGoInstance },
{ icon: "TRACE", title: "Dashboard", func: handleGoDashboard },
{ icon: "ALARM", title: "Alarm", func: handleGoAlarm },
{ icon: "" },
{ icon: "" },
]);
// legend // legend
legend.value = graph.value.append("g").attr("class", "topo-legend"); legend.value = graph.value.append("g").attr("class", "topo-legend");
topoLegend(legend.value, height.value, width.value); topoLegend(legend.value, height.value, width.value);
svg.value.on("click", (event: any) => { svg.value.on("click", (event: any) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
tools.value.attr("style", "display: none"); topologyStore.setNode(null);
showSetting.value = false;
}); });
}); });
function ticked() { function ticked() {
@ -156,13 +166,15 @@ function dragended(d: any) {
simulation.value.alphaTarget(0); simulation.value.alphaTarget(0);
} }
} }
function handleNodeClick(d: Node) { function handleNodeClick(d: Node & { x: number; y: number }) {
topologyStore.setNode(d); topologyStore.setNode(d);
topologyStore.setLink({}); topologyStore.setLink(null);
operationsPos.x = d.x;
operationsPos.y = d.y + 30;
} }
function handleLinkClick(event: any, d: Call) { function handleLinkClick(event: any, d: Call) {
event.stopPropagation(); event.stopPropagation();
topologyStore.setNode({}); topologyStore.setNode(null);
topologyStore.setLink(d); topologyStore.setLink(d);
const path = `/dashboard/${dashboardStore.layerId}/${dashboardStore.entity}Relation/${d.source.id}/${d.target.id}/${settings.value.linkDashboard}`; const path = `/dashboard/${dashboardStore.layerId}/${dashboardStore.entity}Relation/${d.source.id}/${d.target.id}/${settings.value.linkDashboard}`;
const routeUrl = router.resolve({ path }); const routeUrl = router.resolve({ path });
@ -175,7 +187,6 @@ function update() {
node.value = nodeElement( node.value = nodeElement(
d3, d3,
node.value.enter(), node.value.enter(),
tools.value,
{ {
dragstart: dragstart, dragstart: dragstart,
dragged: dragged, dragged: dragged,
@ -321,11 +332,32 @@ watch(
background-color: #2b3037; background-color: #2b3037;
overflow: auto; overflow: auto;
padding: 0 10px; padding: 0 10px;
border-radius: 4px; border-radius: 3px;
color: #ccc; color: #ccc;
transition: all 0.5ms linear; transition: all 0.5ms linear;
} }
.operations-list {
position: absolute;
padding: 10px;
color: #333;
cursor: pointer;
background-color: #fff;
border-radius: 3px;
span {
display: block;
height: 30px;
width: 100px;
line-height: 30px;
text-align: center;
}
span:hover {
color: #217ef2;
}
}
.switch-icon { .switch-icon {
position: absolute; position: absolute;
top: 22px; top: 22px;

View File

@ -19,11 +19,8 @@ export const linkElement = (graph: any) => {
const linkEnter = graph const linkEnter = graph
.append("path") .append("path")
.attr("class", "topo-line") .attr("class", "topo-line")
// .attr("stroke-width", (d: { cpm: number }) => (d.cpm ? 8 : 2))
.attr("marker-end", "url(#arrow)") .attr("marker-end", "url(#arrow)")
.attr("stroke", (d: { cpm: number }) => .attr("stroke", "#217EF25f");
d.cpm ? "#217EF25f" : "#6a6d7777"
);
return linkEnter; return linkEnter;
}; };
export const anchorElement = (graph: any, funcs: any, tip: any) => { export const anchorElement = (graph: any, funcs: any, tip: any) => {
@ -31,7 +28,7 @@ export const anchorElement = (graph: any, funcs: any, tip: any) => {
.append("circle") .append("circle")
.attr("class", "topo-line-anchor") .attr("class", "topo-line-anchor")
.attr("r", 5) .attr("r", 5)
.attr("fill", (d: { cpm: number }) => (d.cpm ? "#217EF25f" : "#6a6d7777")) .attr("fill", "#217EF25f")
.on("mouseover", function (event: unknown, d: unknown) { .on("mouseover", function (event: unknown, d: unknown) {
tip.html(funcs.$tip).show(d, this); tip.html(funcs.$tip).show(d, this);
}) })
@ -58,9 +55,6 @@ export const arrowMarker = (graph: any) => {
.attr("orient", "auto"); .attr("orient", "auto");
const arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2"; const arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2";
arrow arrow.append("path").attr("d", arrowPath).attr("fill", "#217EF25f");
.append("path")
.attr("d", arrowPath)
.attr("fill", (d: { cpm: number }) => (d.cpm ? "#217EF25f" : "#6a6d7777"));
return arrow; return arrow;
}; };

View File

@ -18,14 +18,7 @@ import icons from "@/assets/img/icons";
import { Node } from "@/types/topology"; import { Node } from "@/types/topology";
icons["KAFKA-CONSUMER"] = icons.KAFKA; icons["KAFKA-CONSUMER"] = icons.KAFKA;
export default ( export default (d3: any, graph: any, funcs: any, tip: any, t: any) => {
d3: any,
graph: any,
tool: any,
funcs: any,
tip: any,
t: any
) => {
const nodeEnter = graph const nodeEnter = graph
.append("g") .append("g")
.call( .call(
@ -59,13 +52,7 @@ export default (
.on("click", (event: any, d: Node | any) => { .on("click", (event: any, d: Node | any) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
tool.attr("style", "display: none");
funcs.handleNodeClick(d); funcs.handleNodeClick(d);
if (d.isReal) {
tool
.attr("transform", `translate(${d.x},${d.y - 20})`)
.attr("style", "display: block");
}
}); });
nodeEnter nodeEnter
.append("image") .append("image")

View File

@ -1,61 +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.
*/
import icons from "@/assets/img/icons";
import { Node } from "@/types/topology";
const Hexagon = (side: number, r: number, cx: number, cy: number) => {
let path = "";
for (let i = 0; i < side; i += 1) {
const x = Math.cos(((2 / side) * i + 1 / side) * Math.PI) * r + cx;
const y = -Math.sin(((2 / side) * i + 1 / side) * Math.PI) * r + cy;
path += !i ? `M${x},${y} ` : `L${x},${y} `;
if (i == side - 1) path += "Z";
}
return path;
};
export default (graph: any, tip: any, data: any) => {
const tool = graph.append("g").attr("class", "topo-tool");
const side = 6;
for (let i = 0; i < data.length; i += 1) {
const x = Math.cos((2 / side) * i * Math.PI) * 34;
const y = -Math.sin((2 / side) * i * Math.PI) * 34;
const tool_g = tool
.append("g")
.attr("class", "topo-tool-i")
.on("mouseover", function (event: any, d: Node) {
tip.html(() => data[i].title).show(d, this);
})
.on("mouseout", function () {
tip.hide(this);
})
.on("click", data[i].func);
tool_g
.append("path")
.attr("class", "tool-hexagon")
.attr("d", Hexagon(6, 17, x, y));
tool_g
.append("svg:image")
.attr("width", 14)
.attr("height", 14)
.attr("x", x - 7)
.attr("y", y - 7)
.attr("style", "opacity: 0.8")
.attr("xlink:href", icons[data[i].icon]);
}
return tool;
};