mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-12 20:25:24 +00:00
feat: click nodes and links
This commit is contained in:
parent
d2de2dab66
commit
6798b33029
@ -20,32 +20,74 @@ limitations under the License. -->
|
|||||||
element-loading-background="rgba(0, 0, 0, 0)"
|
element-loading-background="rgba(0, 0, 0, 0)"
|
||||||
:style="`height: ${height}px`"
|
:style="`height: ${height}px`"
|
||||||
>
|
>
|
||||||
<svg :width="width - 100" :height="height" style="background-color: #fff">
|
<svg :width="width - 100" :height="height" style="background-color: #fff" @click="handleSvg($event)">
|
||||||
<g v-for="(n, index) in topologyLayout.nodes" :key="index">
|
<g
|
||||||
|
v-for="(n, index) in topologyLayout.nodes"
|
||||||
|
:key="index"
|
||||||
|
@mouseout="hideTip"
|
||||||
|
@mouseover="showNodeTip($event, n)"
|
||||||
|
@click="handleNodeClick($event, n)"
|
||||||
|
class="topo-node"
|
||||||
|
>
|
||||||
<circle
|
<circle
|
||||||
class="node"
|
class="node"
|
||||||
r="18"
|
r="18"
|
||||||
stroke-width="6"
|
stroke-width="6"
|
||||||
:stroke="n.isReal ? '#72c59f' : 'red'"
|
:stroke="n.isReal ? '#72c59f' : '#ed374d'"
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
:cx="n.x"
|
:cx="n.x"
|
||||||
:cy="n.y"
|
:cy="n.y"
|
||||||
/>
|
/>
|
||||||
|
<image
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
:x="n.x - 8"
|
||||||
|
:y="n.y - 10"
|
||||||
|
:href="!n.type || n.type === `N/A` ? icons.UNDEFINED : icons[n.type.toUpperCase().replace('-', '')]"
|
||||||
|
/>
|
||||||
<text :x="n.x - (n.name.length * 6) / 2" :y="n.y + n.height + 12" style="pointer-events: none">
|
<text :x="n.x - (n.name.length * 6) / 2" :y="n.y + n.height + 12" style="pointer-events: none">
|
||||||
{{ n.name.length > 20 ? `${n.name.substring(0, 20)}...` : n.name }}
|
{{ n.name.length > 20 ? `${n.name.substring(0, 20)}...` : n.name }}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<path
|
<g v-for="(l, index) in topologyLayout.calls" :key="index">
|
||||||
v-for="(l, index) in topologyLayout.calls"
|
<path
|
||||||
:key="index"
|
class="topo-line"
|
||||||
class="link"
|
:d="`M${l.sourceObj.x} ${l.sourceObj.y}
|
||||||
:d="`M${l.sourceObj.x} ${l.sourceObj.y}
|
L${l.targetObj.x} ${l.targetObj.y}`"
|
||||||
L${l.targetObj.x} ${l.targetObj.y}`"
|
stroke="#aaa"
|
||||||
stroke="#999"
|
stroke-width="1"
|
||||||
stroke-width="1"
|
marker-end="url(#arrow)"
|
||||||
/>
|
/>
|
||||||
|
<circle
|
||||||
|
class="topo-line-anchor"
|
||||||
|
:cx="(l.sourceObj.x + l.targetObj.x) / 2"
|
||||||
|
:cy="(l.sourceObj.y + l.targetObj.y) / 2"
|
||||||
|
r="4"
|
||||||
|
fill="#aaa"
|
||||||
|
@click="handleLinkClick($event, l)"
|
||||||
|
@mouseover="showLinkTip($event, l)"
|
||||||
|
@mouseout="hideTip"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g class="arrows">
|
||||||
|
<defs v-for="(_, index) in topologyLayout.calls" :key="index">
|
||||||
|
<marker
|
||||||
|
id="arrow"
|
||||||
|
markerUnits="strokeWidth"
|
||||||
|
markerWidth="8"
|
||||||
|
markerHeight="8"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
refX="10"
|
||||||
|
refY="6"
|
||||||
|
orient="auto"
|
||||||
|
>
|
||||||
|
<path d="M2,2 L10,6 L2,10 L6,6 L2,2" fill="#999" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- <div class="legend">
|
<div id="tooltip"></div>
|
||||||
|
<div class="legend">
|
||||||
<div>
|
<div>
|
||||||
<img :src="icons.CUBE" />
|
<img :src="icons.CUBE" />
|
||||||
<span>
|
<span>
|
||||||
@ -85,7 +127,7 @@ limitations under the License. -->
|
|||||||
<span v-for="(item, index) of items" :key="index" @click="item.func(item.dashboard)">
|
<span v-for="(item, index) of items" :key="index" @click="item.func(item.dashboard)">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</span>
|
</span>
|
||||||
</div> -->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -93,11 +135,8 @@ limitations under the License. -->
|
|||||||
import { ref, onMounted, onBeforeUnmount, reactive, watch, computed, nextTick } from "vue";
|
import { ref, onMounted, onBeforeUnmount, reactive, watch, computed, nextTick } 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 zoom from "../../components/utils/zoom";
|
import zoom from "../../components/utils/zoom";
|
||||||
import { simulationInit, simulationSkip } from "./utils/simulation";
|
// import { simulationInit, simulationSkip } from "./utils/simulation";
|
||||||
import nodeElement from "./utils/nodeElement";
|
|
||||||
import { linkElement, anchorElement, arrowMarker } from "./utils/linkElement";
|
|
||||||
import type { Node, Call } from "@/types/topology";
|
import type { Node, Call } from "@/types/topology";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { useTopologyStore } from "@/store/modules/topology";
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
@ -134,12 +173,10 @@ limitations under the License. -->
|
|||||||
const simulation = ref<any>(null);
|
const simulation = ref<any>(null);
|
||||||
const svg = ref<Nullable<any>>(null);
|
const svg = ref<Nullable<any>>(null);
|
||||||
const chart = ref<Nullable<HTMLDivElement>>(null);
|
const chart = ref<Nullable<HTMLDivElement>>(null);
|
||||||
const tip = ref<Nullable<HTMLDivElement>>(null);
|
|
||||||
const graph = ref<any>(null);
|
const graph = ref<any>(null);
|
||||||
const node = ref<any>(null);
|
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 showSetting = ref<boolean>(false);
|
const showSetting = ref<boolean>(false);
|
||||||
const settings = ref<any>(props.config);
|
const settings = ref<any>(props.config);
|
||||||
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
|
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
|
||||||
@ -147,6 +184,7 @@ limitations under the License. -->
|
|||||||
const graphConfig = computed(() => props.config.graph || {});
|
const graphConfig = computed(() => props.config.graph || {});
|
||||||
const depth = ref<number>(graphConfig.value.depth || 2);
|
const depth = ref<number>(graphConfig.value.depth || 2);
|
||||||
const topologyLayout = ref<any>({});
|
const topologyLayout = ref<any>({});
|
||||||
|
const tooltip = ref<Nullable<any>>(null);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@ -176,9 +214,8 @@ limitations under the License. -->
|
|||||||
// svg.value = d3.select(chart.value).append("svg").attr("class", "topo-svg");
|
// svg.value = d3.select(chart.value).append("svg").attr("class", "topo-svg");
|
||||||
await initLegendMetrics();
|
await initLegendMetrics();
|
||||||
draw();
|
draw();
|
||||||
// await init();
|
tooltip.value = d3.select("#tooltip");
|
||||||
// update();
|
setNodeTools(settings.value.nodeDashboard);
|
||||||
// setNodeTools(settings.value.nodeDashboard);
|
|
||||||
});
|
});
|
||||||
function draw() {
|
function draw() {
|
||||||
const levels = [];
|
const levels = [];
|
||||||
@ -212,26 +249,14 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
topologyLayout.value = layout(levels, topologyStore.calls);
|
topologyLayout.value = layout(levels, topologyStore.calls);
|
||||||
console.log(topologyLayout.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
function handleSvg(event: MouseEvent) {
|
||||||
tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]);
|
event.stopPropagation();
|
||||||
graph.value = svg.value.append("g").attr("class", "topo-svg-graph").attr("transform", `translate(-100, -100)`);
|
event.preventDefault();
|
||||||
graph.value.call(tip.value);
|
topologyStore.setNode(null);
|
||||||
simulation.value = simulationInit(d3, topologyStore.nodes, topologyStore.calls, ticked);
|
topologyStore.setLink(null);
|
||||||
node.value = graph.value.append("g").selectAll(".topo-node");
|
dashboardStore.selectWidget(props.config);
|
||||||
link.value = graph.value.append("g").selectAll(".topo-line");
|
|
||||||
anchor.value = graph.value.append("g").selectAll(".topo-line-anchor");
|
|
||||||
arrow.value = graph.value.append("g").selectAll(".topo-line-arrow");
|
|
||||||
svg.value.call(zoom(d3, graph.value, [-100, -100]));
|
|
||||||
svg.value.on("click", (event: PointerEvent) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
topologyStore.setNode(null);
|
|
||||||
topologyStore.setLink(null);
|
|
||||||
dashboardStore.selectWidget(props.config);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initLegendMetrics() {
|
async function initLegendMetrics() {
|
||||||
@ -245,6 +270,65 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function showNodeTip(event: MouseEvent, data: Node) {
|
||||||
|
const nodeMetrics: string[] = settings.value.nodeMetrics || [];
|
||||||
|
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
|
||||||
|
const html = nodeMetrics.map((m, index) => {
|
||||||
|
const metric =
|
||||||
|
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || {};
|
||||||
|
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>`;
|
||||||
|
});
|
||||||
|
const tipHtml = [` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`, ...html].join(" ");
|
||||||
|
|
||||||
|
tooltip.value
|
||||||
|
.style("top", event.offsetY + "px")
|
||||||
|
.style("left", event.offsetX + "px")
|
||||||
|
.style("visibility", "visible")
|
||||||
|
.html(tipHtml);
|
||||||
|
}
|
||||||
|
function showLinkTip(event: MouseEvent, 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, index) => {
|
||||||
|
const metric = topologyStore.linkServerMetrics[m].values.find(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||||
|
);
|
||||||
|
if (metric) {
|
||||||
|
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: string, index: number) => {
|
||||||
|
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
|
||||||
|
const metric = topologyStore.linkClientMetrics[m].values.find(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||||
|
);
|
||||||
|
if (metric) {
|
||||||
|
const v = aggregation(metric.value, opt);
|
||||||
|
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const html = [
|
||||||
|
...htmlServer,
|
||||||
|
...htmlClient,
|
||||||
|
`<div><span class="grey">${t("detectPoint")}:</span>${data.detectPoints.join(" | ")}</div>`,
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
tooltip.value
|
||||||
|
.style("top", event.offsetY + "px")
|
||||||
|
.style("left", event.offsetX + "px")
|
||||||
|
.style("visibility", "visible")
|
||||||
|
.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTip() {
|
||||||
|
tooltip.value.style("visibility", "hidden");
|
||||||
|
}
|
||||||
function ticked() {
|
function ticked() {
|
||||||
link.value.attr(
|
link.value.attr(
|
||||||
"d",
|
"d",
|
||||||
@ -281,7 +365,8 @@ limitations under the License. -->
|
|||||||
simulation.value.alphaTarget(0);
|
simulation.value.alphaTarget(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleNodeClick(event: PointerEvent, d: Node & { x: number; y: number }) {
|
function handleNodeClick(event: MouseEvent, d: Node & { x: number; y: number }) {
|
||||||
|
hideTip();
|
||||||
topologyStore.setNode(d);
|
topologyStore.setNode(d);
|
||||||
topologyStore.setLink(null);
|
topologyStore.setLink(null);
|
||||||
operationsPos.x = event.offsetX;
|
operationsPos.x = event.offsetX;
|
||||||
@ -294,7 +379,7 @@ limitations under the License. -->
|
|||||||
{ id: "alarm", title: "Alarm", func: handleGoAlarm },
|
{ id: "alarm", title: "Alarm", func: handleGoAlarm },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
function handleLinkClick(event: PointerEvent, d: Call) {
|
function handleLinkClick(event: MouseEvent, d: Call) {
|
||||||
if (d.source.layer !== dashboardStore.layerId || d.target.layer !== dashboardStore.layerId) {
|
if (d.source.layer !== dashboardStore.layerId || d.target.layer !== dashboardStore.layerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -321,114 +406,6 @@ limitations under the License. -->
|
|||||||
window.open(routeUrl.href, "_blank");
|
window.open(routeUrl.href, "_blank");
|
||||||
dashboardStore.setEntity(origin);
|
dashboardStore.setEntity(origin);
|
||||||
}
|
}
|
||||||
function update() {
|
|
||||||
// node element
|
|
||||||
if (!node.value || !link.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
node.value = node.value.data(topologyStore.nodes, (d: Node) => d.id);
|
|
||||||
node.value.exit().remove();
|
|
||||||
node.value = nodeElement(
|
|
||||||
d3,
|
|
||||||
node.value.enter(),
|
|
||||||
{
|
|
||||||
dragstart: dragstart,
|
|
||||||
dragged: dragged,
|
|
||||||
dragended: dragended,
|
|
||||||
handleNodeClick: handleNodeClick,
|
|
||||||
tipHtml: (data: Node) => {
|
|
||||||
const nodeMetrics: string[] = settings.value.nodeMetrics || [];
|
|
||||||
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
|
|
||||||
const html = nodeMetrics.map((m, index) => {
|
|
||||||
const metric =
|
|
||||||
topologyStore.nodeMetricValue[m].values.find(
|
|
||||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
|
||||||
) || {};
|
|
||||||
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>`, ...html].join(" ");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tip.value,
|
|
||||||
settings.value.legend,
|
|
||||||
).merge(node.value);
|
|
||||||
// line element
|
|
||||||
link.value = link.value.data(topologyStore.calls, (d: Call) => d.id);
|
|
||||||
link.value.exit().remove();
|
|
||||||
link.value = linkElement(link.value.enter()).merge(link.value);
|
|
||||||
// anchorElement
|
|
||||||
anchor.value = anchor.value.data(topologyStore.calls, (d: Call) => d.id);
|
|
||||||
anchor.value.exit().remove();
|
|
||||||
anchor.value = anchorElement(
|
|
||||||
anchor.value.enter(),
|
|
||||||
{
|
|
||||||
handleLinkClick: handleLinkClick,
|
|
||||||
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, index) => {
|
|
||||||
const metric = topologyStore.linkServerMetrics[m].values.find(
|
|
||||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
|
||||||
);
|
|
||||||
if (metric) {
|
|
||||||
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: string, index: number) => {
|
|
||||||
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
|
|
||||||
const metric = topologyStore.linkClientMetrics[m].values.find(
|
|
||||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
|
||||||
);
|
|
||||||
if (metric) {
|
|
||||||
const v = aggregation(metric.value, opt);
|
|
||||||
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const html = [
|
|
||||||
...htmlServer,
|
|
||||||
...htmlClient,
|
|
||||||
`<div><span class="grey">${t("detectPoint")}:</span>${data.detectPoints.join(" | ")}</div>`,
|
|
||||||
].join(" ");
|
|
||||||
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tip.value,
|
|
||||||
).merge(anchor.value);
|
|
||||||
// arrow marker
|
|
||||||
arrow.value = arrow.value.data(topologyStore.calls, (d: Call) => d.id);
|
|
||||||
arrow.value.exit().remove();
|
|
||||||
arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value);
|
|
||||||
// force element
|
|
||||||
simulation.value.nodes(topologyStore.nodes);
|
|
||||||
simulation.value
|
|
||||||
.force("link")
|
|
||||||
.links(topologyStore.calls)
|
|
||||||
.id((d: Call) => d.id);
|
|
||||||
simulationSkip(d3, simulation.value, ticked);
|
|
||||||
const loopMap: any = {};
|
|
||||||
for (let i = 0; i < topologyStore.calls.length; i++) {
|
|
||||||
const link: any = topologyStore.calls[i];
|
|
||||||
link.loopFactor = 1;
|
|
||||||
for (let j = 0; j < topologyStore.calls.length; j++) {
|
|
||||||
if (i === j || loopMap[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const otherLink = topologyStore.calls[j];
|
|
||||||
if (link.source.id === otherLink.target.id && link.target.id === otherLink.source.id) {
|
|
||||||
link.loopFactor = -1;
|
|
||||||
loopMap[j] = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function handleInspect() {
|
async function handleInspect() {
|
||||||
svg.value.selectAll(".topo-svg-graph").remove();
|
svg.value.selectAll(".topo-svg-graph").remove();
|
||||||
const id = topologyStore.node.id;
|
const id = topologyStore.node.id;
|
||||||
@ -440,8 +417,6 @@ limitations under the License. -->
|
|||||||
if (resp && resp.errors) {
|
if (resp && resp.errors) {
|
||||||
ElMessage.error(resp.errors);
|
ElMessage.error(resp.errors);
|
||||||
}
|
}
|
||||||
await init();
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
function handleGoEndpoint(name: string) {
|
function handleGoEndpoint(name: string) {
|
||||||
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${topologyStore.node.id}/${name}`;
|
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${topologyStore.node.id}/${name}`;
|
||||||
@ -479,8 +454,6 @@ limitations under the License. -->
|
|||||||
if (resp && resp.errors) {
|
if (resp && resp.errors) {
|
||||||
ElMessage.error(resp.errors);
|
ElMessage.error(resp.errors);
|
||||||
}
|
}
|
||||||
await init();
|
|
||||||
update();
|
|
||||||
topologyStore.setNode(null);
|
topologyStore.setNode(null);
|
||||||
topologyStore.setLink(null);
|
topologyStore.setLink(null);
|
||||||
}
|
}
|
||||||
@ -547,8 +520,6 @@ limitations under the License. -->
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
svg.value.selectAll(".topo-svg-graph").remove();
|
svg.value.selectAll(".topo-svg-graph").remove();
|
||||||
await init();
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeDepth(opt: Option[] | any) {
|
async function changeDepth(opt: Option[] | any) {
|
||||||
@ -684,7 +655,8 @@ limitations under the License. -->
|
|||||||
animation: topo-dash 0.5s linear infinite;
|
animation: topo-dash 0.5s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topo-line-anchor {
|
.topo-line-anchor,
|
||||||
|
.topo-node {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,34 +667,6 @@ limitations under the License. -->
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.d3-tip {
|
|
||||||
line-height: 1;
|
|
||||||
padding: 8px;
|
|
||||||
color: #eee;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
z-index: 9999;
|
|
||||||
background: #252a2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.d3-tip:after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
font-size: 10px;
|
|
||||||
width: 100%;
|
|
||||||
line-height: 0.8;
|
|
||||||
color: #252a2f;
|
|
||||||
content: "\25BC";
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.d3-tip.n:after {
|
|
||||||
margin: -2px 0 0 0;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
@keyframes topo-dash {
|
@keyframes topo-dash {
|
||||||
from {
|
from {
|
||||||
stroke-dashoffset: 20;
|
stroke-dashoffset: 20;
|
||||||
@ -736,4 +680,13 @@ limitations under the License. -->
|
|||||||
.el-loading-spinner {
|
.el-loading-spinner {
|
||||||
top: 30%;
|
top: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tooltip {
|
||||||
|
position: absolute;
|
||||||
|
visibility: hidden;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user