mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-14 09:00:50 +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 Ebpf from "./Ebpf.vue";
|
||||||
import DemandLog from "./DemandLog.vue";
|
import DemandLog from "./DemandLog.vue";
|
||||||
import Event from "./Event.vue";
|
import Event from "./Event.vue";
|
||||||
import NetworkProfiling from "./NetworkProfiling.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Tab,
|
Tab,
|
||||||
@ -37,5 +36,4 @@ export default {
|
|||||||
Ebpf,
|
Ebpf,
|
||||||
DemandLog,
|
DemandLog,
|
||||||
Event,
|
Event,
|
||||||
NetworkProfiling,
|
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,6 @@ import Text from "./Text.vue";
|
|||||||
import Ebpf from "./Ebpf.vue";
|
import Ebpf from "./Ebpf.vue";
|
||||||
import DemandLog from "./DemandLog.vue";
|
import DemandLog from "./DemandLog.vue";
|
||||||
import Event from "./Event.vue";
|
import Event from "./Event.vue";
|
||||||
import NetworkProfiling from "./NetworkProfiling.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Widget,
|
Widget,
|
||||||
@ -35,5 +34,4 @@ export default {
|
|||||||
Ebpf,
|
Ebpf,
|
||||||
DemandLog,
|
DemandLog,
|
||||||
Event,
|
Event,
|
||||||
NetworkProfiling,
|
|
||||||
};
|
};
|
||||||
|
@ -198,11 +198,6 @@ export const InstanceTools = [
|
|||||||
{ name: "assignment", content: "Add Log", id: "addLog" },
|
{ name: "assignment", content: "Add Log", id: "addLog" },
|
||||||
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
|
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
|
||||||
{ name: "event", content: "Add Event", id: "addEvent" },
|
{ name: "event", content: "Add Event", id: "addEvent" },
|
||||||
{
|
|
||||||
name: "timeline",
|
|
||||||
content: "Add Network Profiling",
|
|
||||||
id: "addNetworkProfiling",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
export const EndpointTools = [
|
export const EndpointTools = [
|
||||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||||
|
@ -548,9 +548,6 @@ function setTabControls(id: string) {
|
|||||||
case "addEvent":
|
case "addEvent":
|
||||||
dashboardStore.addTabControls("Event");
|
dashboardStore.addTabControls("Event");
|
||||||
break;
|
break;
|
||||||
case "addNetworkProfiling":
|
|
||||||
dashboardStore.addTabControls("NetworkProfiling");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
ElMessage.info("Don't support this control");
|
ElMessage.info("Don't support this control");
|
||||||
break;
|
break;
|
||||||
@ -589,9 +586,6 @@ function setControls(id: string) {
|
|||||||
case "addEvent":
|
case "addEvent":
|
||||||
dashboardStore.addControl("Event");
|
dashboardStore.addControl("Event");
|
||||||
break;
|
break;
|
||||||
case "addNetworkProfiling":
|
|
||||||
dashboardStore.addControl("NetworkProfiling");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
dashboardStore.addControl("Widget");
|
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