mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-14 00:37:33 +00:00
remove widget
This commit is contained in:
parent
12dd47affe
commit
260e0f2a68
@ -1,154 +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 { defineStore } from "pinia";
|
||||
import { EBPFTaskList, ProcessNode } from "@/types/ebpf";
|
||||
import { store } from "@/store";
|
||||
import graphql from "@/graphql";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Call } from "@/types/topology";
|
||||
import { LayoutConfig } from "@/types/dashboard";
|
||||
|
||||
interface NetworkProfilingState {
|
||||
networkTasks: EBPFTaskList[];
|
||||
networkTip: string;
|
||||
selectedNetworkTask: Recordable<EBPFTaskList>;
|
||||
nodes: ProcessNode[];
|
||||
calls: Call[];
|
||||
node: Nullable<ProcessNode>;
|
||||
call: Nullable<Call>;
|
||||
metricsLayout: LayoutConfig[];
|
||||
selectedMetric: Nullable<LayoutConfig>;
|
||||
activeMetricIndex: string;
|
||||
}
|
||||
|
||||
export const networkProfilingStore = defineStore({
|
||||
id: "networkProfiling",
|
||||
state: (): NetworkProfilingState => ({
|
||||
networkTasks: [],
|
||||
networkTip: "",
|
||||
selectedNetworkTask: {},
|
||||
nodes: [],
|
||||
calls: [],
|
||||
node: null,
|
||||
call: null,
|
||||
metricsLayout: [],
|
||||
selectedMetric: null,
|
||||
activeMetricIndex: "",
|
||||
}),
|
||||
actions: {
|
||||
setSelectedNetworkTask(task: EBPFTaskList) {
|
||||
this.selectedNetworkTask = task || {};
|
||||
},
|
||||
setNode(node: Node) {
|
||||
this.node = node;
|
||||
},
|
||||
setLink(link: Call) {
|
||||
this.call = link;
|
||||
},
|
||||
setMetricsLayout(layout: LayoutConfig[]) {
|
||||
this.metricsLayout = layout;
|
||||
},
|
||||
setSelectedMetric(item: LayoutConfig) {
|
||||
this.selectedMetric = item;
|
||||
},
|
||||
setActiveItem(index: string) {
|
||||
this.activeMetricIndex = index;
|
||||
},
|
||||
setTopology(data: { nodes: ProcessNode[]; calls: Call[] }) {
|
||||
const obj = {} as any;
|
||||
const calls = (data.calls || []).reduce((prev: Call[], next: Call) => {
|
||||
if (!obj[next.id]) {
|
||||
obj[next.id] = true;
|
||||
next.value = next.value || 1;
|
||||
for (const node of data.nodes) {
|
||||
if (next.source === node.id) {
|
||||
next.sourceObj = node;
|
||||
}
|
||||
if (next.target === node.id) {
|
||||
next.targetObj = node;
|
||||
}
|
||||
}
|
||||
next.value = next.value || 1;
|
||||
prev.push(next);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
this.calls = calls;
|
||||
this.nodes = data.nodes;
|
||||
},
|
||||
async createNetworkTask(param: {
|
||||
serviceId: string;
|
||||
serviceInstanceId: string;
|
||||
}) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("newNetworkProfiling")
|
||||
.params({ request: { instanceId: param.serviceInstanceId } });
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.getTaskList({
|
||||
...param,
|
||||
targets: ["NETWORK"],
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
async getTaskList(params: {
|
||||
serviceId: string;
|
||||
serviceInstanceId: string;
|
||||
targets: string[];
|
||||
}) {
|
||||
if (!params.serviceId) {
|
||||
return new Promise((resolve) => resolve({}));
|
||||
}
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("getEBPFTasks")
|
||||
.params(params);
|
||||
|
||||
this.networkTip = "";
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.networkTasks = res.data.data.queryEBPFTasks || [];
|
||||
this.selectedNetworkTask = this.networkTasks[0] || {};
|
||||
this.setSelectedNetworkTask(this.selectedNetworkTask);
|
||||
return res.data;
|
||||
},
|
||||
async getProcessTopology(params: {
|
||||
duration: any;
|
||||
serviceInstanceId: string;
|
||||
}) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("getProcessTopology")
|
||||
.params(params);
|
||||
if (res.data.errors) {
|
||||
this.nodes = [];
|
||||
this.calls = [];
|
||||
return res.data;
|
||||
}
|
||||
const { topology } = res.data.data;
|
||||
|
||||
this.setTopology(topology);
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function useNetworkProfilingStore(): any {
|
||||
return networkProfilingStore(store);
|
||||
}
|
@ -1,97 +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="profile-wrapper flex-v">
|
||||
<div class="title">Network Profiling</div>
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
:width="100"
|
||||
v-if="dashboardStore.editMode"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="operation cp">
|
||||
<Icon iconName="ellipsis_v" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<div class="tools" @click="removeWidget">
|
||||
<span>{{ t("delete") }}</span>
|
||||
</div>
|
||||
</el-popover>
|
||||
<Content />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import Content from "../related/network-profiling/Content.vue";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
activeIndex: { type: String, default: "" },
|
||||
needQuery: { type: Boolean, default: true },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
function removeWidget() {
|
||||
dashboardStore.removeControls(props.data);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.operation {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.tools {
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
</style>
|
@ -24,7 +24,6 @@ import Text from "./Text.vue";
|
||||
import Ebpf from "./Ebpf.vue";
|
||||
import DemandLog from "./DemandLog.vue";
|
||||
import Event from "./Event.vue";
|
||||
import NetworkProfiling from "./NetworkProfiling.vue";
|
||||
|
||||
export default {
|
||||
Tab,
|
||||
@ -37,5 +36,4 @@ export default {
|
||||
Ebpf,
|
||||
DemandLog,
|
||||
Event,
|
||||
NetworkProfiling,
|
||||
};
|
||||
|
@ -23,7 +23,6 @@ import Text from "./Text.vue";
|
||||
import Ebpf from "./Ebpf.vue";
|
||||
import DemandLog from "./DemandLog.vue";
|
||||
import Event from "./Event.vue";
|
||||
import NetworkProfiling from "./NetworkProfiling.vue";
|
||||
|
||||
export default {
|
||||
Widget,
|
||||
@ -35,5 +34,4 @@ export default {
|
||||
Ebpf,
|
||||
DemandLog,
|
||||
Event,
|
||||
NetworkProfiling,
|
||||
};
|
||||
|
@ -198,11 +198,6 @@ export const InstanceTools = [
|
||||
{ name: "assignment", content: "Add Log", id: "addLog" },
|
||||
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
|
||||
{ name: "event", content: "Add Event", id: "addEvent" },
|
||||
{
|
||||
name: "timeline",
|
||||
content: "Add Network Profiling",
|
||||
id: "addNetworkProfiling",
|
||||
},
|
||||
];
|
||||
export const EndpointTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
|
@ -548,9 +548,6 @@ function setTabControls(id: string) {
|
||||
case "addEvent":
|
||||
dashboardStore.addTabControls("Event");
|
||||
break;
|
||||
case "addNetworkProfiling":
|
||||
dashboardStore.addTabControls("NetworkProfiling");
|
||||
break;
|
||||
default:
|
||||
ElMessage.info("Don't support this control");
|
||||
break;
|
||||
@ -589,9 +586,6 @@ function setControls(id: string) {
|
||||
case "addEvent":
|
||||
dashboardStore.addControl("Event");
|
||||
break;
|
||||
case "addNetworkProfiling":
|
||||
dashboardStore.addControl("NetworkProfiling");
|
||||
break;
|
||||
default:
|
||||
dashboardStore.addControl("Widget");
|
||||
}
|
||||
|
@ -1,60 +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="flex-h content">
|
||||
<Tasks />
|
||||
<div class="vis-graph ml-5">
|
||||
<div class="schedules">
|
||||
<Schedules />
|
||||
</div>
|
||||
<div class="item" v-show="networkProfilingStore.nodes.length">
|
||||
<process-topology />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import Tasks from "./components/Tasks.vue";
|
||||
import Schedules from "./components/Schedules.vue";
|
||||
import ProcessTopology from "./components/ProcessTopology.vue";
|
||||
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||
|
||||
const networkProfilingStore = useNetworkProfilingStore();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
height: calc(100% - 30px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vis-graph {
|
||||
height: 100%;
|
||||
flex-grow: 2;
|
||||
min-width: 700px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
height: calc(100% - 210px);
|
||||
background-color: #333840;
|
||||
}
|
||||
|
||||
.schedules {
|
||||
height: 200px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
@ -1,272 +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 ref="chart" class="micro-topo-chart"></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import * as d3 from "d3";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import d3tip from "d3-tip";
|
||||
import {
|
||||
simulationInit,
|
||||
simulationSkip,
|
||||
} from "../../components/D3Graph/simulation";
|
||||
import {
|
||||
linkElement,
|
||||
anchorElement,
|
||||
arrowMarker,
|
||||
} from "../../components/D3Graph/linkElement";
|
||||
import nodeElement from "../../components/D3Graph/nodeElement";
|
||||
import { Call } from "@/types/topology";
|
||||
// import zoom from "../../components/D3Graph/zoom";
|
||||
import { ProcessNode } from "@/types/ebpf";
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
|
||||
/*global Nullable, defineProps */
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const networkProfilingStore = useNetworkProfilingStore();
|
||||
const height = ref<number>(100);
|
||||
const width = ref<number>(100);
|
||||
const simulation = ref<any>(null);
|
||||
const svg = ref<Nullable<any>>(null);
|
||||
const chart = ref<Nullable<HTMLDivElement>>(null);
|
||||
const tip = ref<Nullable<HTMLDivElement>>(null);
|
||||
const graph = ref<any>(null);
|
||||
const node = ref<any>(null);
|
||||
const link = ref<any>(null);
|
||||
const anchor = ref<any>(null);
|
||||
const arrow = ref<any>(null);
|
||||
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
oldVal.value = (chart.value && chart.value.getBoundingClientRect()) || {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
});
|
||||
|
||||
async function init() {
|
||||
svg.value = d3.select(chart.value).append("svg").attr("class", "process-svg");
|
||||
if (!networkProfilingStore.nodes.length) {
|
||||
return;
|
||||
}
|
||||
drawGraph();
|
||||
update();
|
||||
}
|
||||
|
||||
function drawGraph() {
|
||||
const dom = chart.value?.getBoundingClientRect() || {
|
||||
height: 20,
|
||||
width: 0,
|
||||
};
|
||||
height.value = dom.height - 20;
|
||||
width.value = dom.width;
|
||||
svg.value.attr("height", height.value).attr("width", width.value);
|
||||
tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]);
|
||||
graph.value = svg.value
|
||||
.append("g")
|
||||
.attr("class", "svg-graph")
|
||||
.attr("transform", `translate(-250, -220)`);
|
||||
graph.value.call(tip.value);
|
||||
simulation.value = simulationInit(
|
||||
d3,
|
||||
networkProfilingStore.nodes,
|
||||
networkProfilingStore.calls,
|
||||
ticked
|
||||
);
|
||||
node.value = graph.value.append("g").selectAll(".topo-node");
|
||||
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));
|
||||
svg.value.on("click", (event: any) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
networkProfilingStore.setNode(null);
|
||||
networkProfilingStore.setLink(null);
|
||||
dashboardStore.selectWidget(props.config);
|
||||
});
|
||||
useThrottleFn(resize, 500)();
|
||||
}
|
||||
function update() {
|
||||
// node element
|
||||
if (!node.value || !link.value) {
|
||||
return;
|
||||
}
|
||||
node.value = node.value.data(
|
||||
networkProfilingStore.nodes,
|
||||
(d: ProcessNode) => d.id
|
||||
);
|
||||
node.value.exit().remove();
|
||||
node.value = nodeElement(
|
||||
d3,
|
||||
node.value.enter(),
|
||||
{
|
||||
tipHtml: (data: ProcessNode) => {
|
||||
return ` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`;
|
||||
},
|
||||
},
|
||||
tip.value
|
||||
).merge(node.value);
|
||||
// line element
|
||||
link.value = link.value.data(networkProfilingStore.calls, (d: Call) => d.id);
|
||||
link.value.exit().remove();
|
||||
link.value = linkElement(link.value.enter()).merge(link.value);
|
||||
// anchorElement
|
||||
anchor.value = anchor.value.data(
|
||||
networkProfilingStore.calls,
|
||||
(d: Call) => d.id
|
||||
);
|
||||
anchor.value.exit().remove();
|
||||
anchor.value = anchorElement(
|
||||
anchor.value.enter(),
|
||||
{
|
||||
handleLinkClick: handleLinkClick,
|
||||
tipHtml: (data: Call) => {
|
||||
const html = `<div><span class="grey">${t(
|
||||
"detectPoint"
|
||||
)}:</span>${data.detectPoints.join(" | ")}</div>`;
|
||||
return html;
|
||||
},
|
||||
},
|
||||
tip.value
|
||||
).merge(anchor.value);
|
||||
// arrow marker
|
||||
arrow.value = arrow.value.data(
|
||||
networkProfilingStore.calls,
|
||||
(d: Call) => d.id
|
||||
);
|
||||
arrow.value.exit().remove();
|
||||
arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value);
|
||||
// force element
|
||||
simulation.value.nodes(networkProfilingStore.nodes);
|
||||
simulation.value
|
||||
.force("link")
|
||||
.links(networkProfilingStore.calls)
|
||||
.id((d: Call) => d.id);
|
||||
simulationSkip(d3, simulation.value, ticked);
|
||||
const loopMap: any = {};
|
||||
for (let i = 0; i < networkProfilingStore.calls.length; i++) {
|
||||
const link: any = networkProfilingStore.calls[i];
|
||||
link.loopFactor = 1;
|
||||
for (let j = 0; j < networkProfilingStore.calls.length; j++) {
|
||||
if (i === j || loopMap[i]) {
|
||||
continue;
|
||||
}
|
||||
const otherLink = networkProfilingStore.calls[j];
|
||||
if (
|
||||
link.source.id === otherLink.target.id &&
|
||||
link.target.id === otherLink.source.id
|
||||
) {
|
||||
link.loopFactor = -1;
|
||||
loopMap[j] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleLinkClick(event: any, d: Call) {
|
||||
if (
|
||||
d.source.layer !== dashboardStore.layerId ||
|
||||
d.target.layer !== dashboardStore.layerId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
networkProfilingStore.setNode(null);
|
||||
networkProfilingStore.setLink(d);
|
||||
}
|
||||
|
||||
function ticked() {
|
||||
link.value.attr(
|
||||
"d",
|
||||
(d: Call | any) =>
|
||||
`M${d.source.x} ${d.source.y} Q ${(d.source.x + d.target.x) / 2} ${
|
||||
(d.target.y + d.source.y) / 2 - d.loopFactor * 90
|
||||
} ${d.target.x} ${d.target.y}`
|
||||
);
|
||||
anchor.value.attr(
|
||||
"transform",
|
||||
(d: Call | any) =>
|
||||
`translate(${(d.source.x + d.target.x) / 2}, ${
|
||||
(d.target.y + d.source.y) / 2 - d.loopFactor * 45
|
||||
})`
|
||||
);
|
||||
node.value.attr(
|
||||
"transform",
|
||||
(d: Node | any) => `translate(${d.x - 22},${d.y - 22})`
|
||||
);
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
const cr = entry.contentRect;
|
||||
if (
|
||||
Math.abs(cr.width - oldVal.value.width) < 5 &&
|
||||
Math.abs(cr.height - oldVal.value.height) < 5
|
||||
) {
|
||||
return;
|
||||
}
|
||||
freshNodes();
|
||||
oldVal.value = { width: cr.width, height: cr.height };
|
||||
});
|
||||
if (chart.value) {
|
||||
observer.observe(chart.value);
|
||||
}
|
||||
}
|
||||
|
||||
async function freshNodes() {
|
||||
svg.value.selectAll(".svg-graph").remove();
|
||||
if (!networkProfilingStore.nodes.length) {
|
||||
return;
|
||||
}
|
||||
drawGraph();
|
||||
update();
|
||||
}
|
||||
watch(
|
||||
() => networkProfilingStore.nodes,
|
||||
() => {
|
||||
freshNodes();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.micro-topo-chart {
|
||||
width: calc(100% - 10px);
|
||||
margin: 0 5px 5px 0;
|
||||
height: 100%;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.process-svg {
|
||||
width: 100%;
|
||||
height: calc(100% - 10px);
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
@ -1,113 +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>
|
||||
<el-button type="primary" size="small">
|
||||
{{ t("start") }}
|
||||
</el-button>
|
||||
<div ref="timeRange" class="time-ranges"></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||
import { DataSet, Timeline } from "vis-timeline/standalone";
|
||||
import "vis-timeline/styles/vis-timeline-graph2d.css";
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
|
||||
/*global Nullable */
|
||||
const { t } = useI18n();
|
||||
const networkProfilingStore = useNetworkProfilingStore();
|
||||
const timeRange = ref<Nullable<HTMLDivElement>>(null);
|
||||
const visGraph = ref<Nullable<any>>(null);
|
||||
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
|
||||
|
||||
onMounted(() => {
|
||||
oldVal.value = (timeRange.value &&
|
||||
timeRange.value.getBoundingClientRect()) || {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
useThrottleFn(resize, 500)();
|
||||
});
|
||||
|
||||
function visTimeline() {
|
||||
if (!timeRange.value) {
|
||||
return;
|
||||
}
|
||||
if (visGraph.value) {
|
||||
visGraph.value.destroy();
|
||||
}
|
||||
if (!networkProfilingStore.selectedNetworkTask.taskId) {
|
||||
return;
|
||||
}
|
||||
const h = timeRange.value.getBoundingClientRect().height;
|
||||
const task = [
|
||||
{
|
||||
id: 1,
|
||||
content: networkProfilingStore.selectedNetworkTask.name,
|
||||
start: new Date(
|
||||
Number(networkProfilingStore.selectedNetworkTask.taskStartTime)
|
||||
),
|
||||
end: new Date(
|
||||
Number(
|
||||
networkProfilingStore.selectedNetworkTask.taskStartTime +
|
||||
networkProfilingStore.selectedNetworkTask.fixedTriggerDuration
|
||||
)
|
||||
),
|
||||
data: networkProfilingStore.selectedNetworkTask,
|
||||
className: networkProfilingStore.selectedNetworkTask.type,
|
||||
},
|
||||
];
|
||||
const items = new DataSet(task);
|
||||
const options: any = {
|
||||
height: h,
|
||||
width: "100%",
|
||||
locale: "en",
|
||||
groupHeightMode: "fitItems",
|
||||
autoResize: false,
|
||||
};
|
||||
visGraph.value = new Timeline(timeRange.value, items, options);
|
||||
}
|
||||
function resize() {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
const cr = entry.contentRect;
|
||||
if (
|
||||
Math.abs(cr.width - oldVal.value.width) < 3 &&
|
||||
Math.abs(cr.height - oldVal.value.height) < 3
|
||||
) {
|
||||
return;
|
||||
}
|
||||
visTimeline();
|
||||
oldVal.value = { width: cr.width, height: cr.height };
|
||||
});
|
||||
if (timeRange.value) {
|
||||
observer.observe(timeRange.value);
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => networkProfilingStore.selectedNetworkTask,
|
||||
() => {
|
||||
visTimeline();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.time-ranges {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0 5px 5px 0;
|
||||
height: 150px;
|
||||
}
|
||||
</style>
|
@ -1,238 +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="profile-task-list flex-v">
|
||||
<div class="profile-task-wrapper flex-v">
|
||||
<div class="profile-t-tool">
|
||||
<span>{{ t("taskList") }}</span>
|
||||
<el-popconfirm
|
||||
title="Are you sure to create a task?"
|
||||
@confirm="createTask"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="new-task cp">
|
||||
<Icon iconName="library_add" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<div class="profile-t-wrapper">
|
||||
<div
|
||||
class="no-data"
|
||||
v-show="!networkProfilingStore.networkTasks.length"
|
||||
>
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
<table class="profile-t">
|
||||
<tr
|
||||
class="profile-tr cp"
|
||||
v-for="(i, index) in networkProfilingStore.networkTasks"
|
||||
@click="changeTask(i)"
|
||||
:key="index"
|
||||
>
|
||||
<td
|
||||
class="profile-td"
|
||||
:class="{
|
||||
selected:
|
||||
networkProfilingStore.selectedNetworkTask.taskId === i.taskId,
|
||||
}"
|
||||
>
|
||||
<div class="ell">
|
||||
<span>
|
||||
{{ i.targetType }}
|
||||
</span>
|
||||
<a class="profile-btn r" @click="viewDetail = true">
|
||||
<Icon iconName="view" size="middle" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="grey ell sm">
|
||||
<span class="mr-10 sm">{{ dateFormat(i.taskStartTime) }}</span>
|
||||
<span class="mr-10 sm">
|
||||
{{
|
||||
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="viewDetail"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="viewDetail = false"
|
||||
>
|
||||
<TaskDetails :details="networkProfilingStore.selectedNetworkTask" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { EBPFTaskList } from "@/types/ebpf";
|
||||
import { ElMessage } from "element-plus";
|
||||
import TaskDetails from "../../components/TaskDetails.vue";
|
||||
import dateFormatStep from "@/utils/dateFormat";
|
||||
import getLocalTime from "@/utils/localtime";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
|
||||
const { t } = useI18n();
|
||||
const selectorStore = useSelectorStore();
|
||||
const networkProfilingStore = useNetworkProfilingStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
const viewDetail = ref<boolean>(false);
|
||||
|
||||
fetchTasks();
|
||||
|
||||
async function changeTask(item: EBPFTaskList) {
|
||||
networkProfilingStore.setSelectedNetworkTask(item);
|
||||
getTopology();
|
||||
}
|
||||
async function getTopology() {
|
||||
const serviceInstanceId =
|
||||
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
|
||||
const resp = await networkProfilingStore.getProcessTopology({
|
||||
serviceInstanceId,
|
||||
duration: {
|
||||
start: dateFormatStep(
|
||||
getLocalTime(
|
||||
appStore.utc,
|
||||
new Date(networkProfilingStore.selectedNetworkTask.taskStartTime)
|
||||
),
|
||||
appStore.duration.step,
|
||||
true
|
||||
),
|
||||
end: dateFormatStep(
|
||||
getLocalTime(
|
||||
appStore.utc,
|
||||
new Date(
|
||||
networkProfilingStore.selectedNetworkTask.taskStartTime +
|
||||
networkProfilingStore.selectedNetworkTask.fixedTriggerDuration *
|
||||
1000
|
||||
)
|
||||
),
|
||||
appStore.duration.step,
|
||||
true
|
||||
),
|
||||
step: appStore.duration.step,
|
||||
},
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
async function createTask() {
|
||||
const serviceId =
|
||||
(selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
const serviceInstanceId =
|
||||
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
|
||||
if (!serviceId) {
|
||||
return;
|
||||
}
|
||||
if (!serviceInstanceId) {
|
||||
return;
|
||||
}
|
||||
networkProfilingStore.createNetworkTask({
|
||||
serviceId,
|
||||
serviceInstanceId,
|
||||
});
|
||||
}
|
||||
async function fetchTasks() {
|
||||
const serviceId =
|
||||
(selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
const serviceInstanceId =
|
||||
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
|
||||
const res = await networkProfilingStore.getTaskList({
|
||||
serviceId,
|
||||
serviceInstanceId,
|
||||
targets: ["NETWORK"],
|
||||
});
|
||||
|
||||
if (res.errors) {
|
||||
return ElMessage.error(res.errors);
|
||||
}
|
||||
getTopology();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-task-list {
|
||||
width: 300px;
|
||||
height: calc(100% - 10px);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.item span {
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.profile-td {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
||||
|
||||
&.selected {
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.profile-t-wrapper {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.profile-t {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.profile-tr {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-t-tool {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
||||
background: #f3f4f9;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-btn {
|
||||
color: #3d444f;
|
||||
padding: 1px 3px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.new-task {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user