mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-14 00:37:33 +00:00
Merge branch 'main' of https://github.com/easonyipj/skywalking-booster-ui
This commit is contained in:
commit
9e3c3c254b
@ -19,7 +19,7 @@ limitations under the License. -->
|
|||||||
#app {
|
#app {
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
min-width: 1024px;
|
min-width: 1024px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -18,7 +18,7 @@ limitations under the License. -->
|
|||||||
<div class="app-config">
|
<div class="app-config">
|
||||||
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
|
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
:value="time"
|
:value="[appStore.durationRow.start, appStore.durationRow.end]"
|
||||||
position="bottom"
|
position="bottom"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
@input="changeTimeRange"
|
@input="changeTimeRange"
|
||||||
@ -55,17 +55,12 @@ import { useI18n } from "vue-i18n";
|
|||||||
import timeFormat from "@/utils/timeFormat";
|
import timeFormat from "@/utils/timeFormat";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import getLocalTime from "@/utils/localtime";
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStoreWithOut();
|
const appStore = useAppStoreWithOut();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const pageName = ref<string>("");
|
const pageName = ref<string>("");
|
||||||
const timeRange = ref<number>(0);
|
const timeRange = ref<number>(0);
|
||||||
const time = ref<Date[]>([
|
|
||||||
appStore.durationRow.start,
|
|
||||||
appStore.durationRow.end,
|
|
||||||
]);
|
|
||||||
|
|
||||||
resetDuration();
|
resetDuration();
|
||||||
getVersion();
|
getVersion();
|
||||||
@ -73,15 +68,13 @@ const setConfig = (value: string) => {
|
|||||||
pageName.value = value || "";
|
pageName.value = value || "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReload = () => {
|
function handleReload() {
|
||||||
const gap =
|
const gap =
|
||||||
appStore.duration.end.getTime() - appStore.duration.start.getTime();
|
appStore.duration.end.getTime() - appStore.duration.start.getTime();
|
||||||
const dates: Date[] = [
|
const dates: Date[] = [new Date(new Date().getTime() - gap), new Date()];
|
||||||
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
|
|
||||||
getLocalTime(appStore.utc, new Date()),
|
|
||||||
];
|
|
||||||
appStore.setDuration(timeFormat(dates));
|
appStore.setDuration(timeFormat(dates));
|
||||||
};
|
}
|
||||||
|
|
||||||
function changeTimeRange(val: Date[] | any) {
|
function changeTimeRange(val: Date[] | any) {
|
||||||
timeRange.value =
|
timeRange.value =
|
||||||
val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
|
val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
|
||||||
@ -114,7 +107,6 @@ function resetDuration() {
|
|||||||
step: d.step,
|
step: d.step,
|
||||||
});
|
});
|
||||||
appStore.updateUTC(d.utc);
|
appStore.updateUTC(d.utc);
|
||||||
time.value = [new Date(d.start), new Date(d.end)];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -141,13 +141,13 @@ const filterMenus = (menus: any[]) => {
|
|||||||
.side-bar {
|
.side-bar {
|
||||||
background: #252a2f;
|
background: #252a2f;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 700px;
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu-vertical:not(.el-menu--collapse) {
|
.el-menu-vertical:not(.el-menu--collapse) {
|
||||||
width: 200px;
|
width: 220px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ span.collapse {
|
|||||||
.menu-control {
|
.menu-control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 7px;
|
top: 7px;
|
||||||
left: 200px;
|
left: 220px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s linear;
|
transition: all 0.2s linear;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
@ -152,6 +152,7 @@ const msg = {
|
|||||||
text: "Text",
|
text: "Text",
|
||||||
query: "Query",
|
query: "Query",
|
||||||
postgreSQL: "PostgreSQL",
|
postgreSQL: "PostgreSQL",
|
||||||
|
endpointTips: "The table shows up to 20 pieces of endpoints.",
|
||||||
seconds: "Seconds",
|
seconds: "Seconds",
|
||||||
hourTip: "Select Hour",
|
hourTip: "Select Hour",
|
||||||
minuteTip: "Select Minute",
|
minuteTip: "Select Minute",
|
||||||
|
@ -152,6 +152,7 @@ const msg = {
|
|||||||
enableAssociate: "Activar asociación",
|
enableAssociate: "Activar asociación",
|
||||||
query: "Consulta",
|
query: "Consulta",
|
||||||
postgreSQL: "PostgreSQL",
|
postgreSQL: "PostgreSQL",
|
||||||
|
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
|
||||||
seconds: "Segundos",
|
seconds: "Segundos",
|
||||||
hourTip: "Seleccione Hora",
|
hourTip: "Seleccione Hora",
|
||||||
minuteTip: "Seleccione Minuto",
|
minuteTip: "Seleccione Minuto",
|
||||||
|
@ -149,6 +149,7 @@ const msg = {
|
|||||||
text: "文本",
|
text: "文本",
|
||||||
query: "查询",
|
query: "查询",
|
||||||
postgreSQL: "PostgreSQL",
|
postgreSQL: "PostgreSQL",
|
||||||
|
endpointTips: "这里最多展示20条endpoints。",
|
||||||
seconds: "秒",
|
seconds: "秒",
|
||||||
hourTip: "选择小时",
|
hourTip: "选择小时",
|
||||||
minuteTip: "选择分钟",
|
minuteTip: "选择分钟",
|
||||||
|
@ -92,15 +92,21 @@ export const networkProfilingStore = defineStore({
|
|||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}, []);
|
}, []);
|
||||||
calls = calls.map((d: any) => {
|
const param = {} as any;
|
||||||
d.sourceId = d.source;
|
calls = data.calls.reduce((prev: (Call | any)[], next: Call | any) => {
|
||||||
d.targetId = d.target;
|
if (param[next.targetId + next.sourceId]) {
|
||||||
d.source = d.sourceObj;
|
next.lowerArc = true;
|
||||||
d.target = d.targetObj;
|
}
|
||||||
delete d.sourceObj;
|
param[next.sourceId + next.targetId] = true;
|
||||||
delete d.targetObj;
|
next.sourceId = next.source;
|
||||||
return d;
|
next.targetId = next.target;
|
||||||
});
|
next.source = next.sourceObj;
|
||||||
|
next.target = next.targetObj;
|
||||||
|
delete next.sourceObj;
|
||||||
|
delete next.targetObj;
|
||||||
|
prev.push(next);
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
this.calls = calls;
|
this.calls = calls;
|
||||||
this.nodes = data.nodes;
|
this.nodes = data.nodes;
|
||||||
},
|
},
|
||||||
|
2
src/types/ebpf.d.ts
vendored
2
src/types/ebpf.d.ts
vendored
@ -74,4 +74,6 @@ export type ProcessNode = {
|
|||||||
serviceInstanceName: string;
|
serviceInstanceName: string;
|
||||||
name: string;
|
name: string;
|
||||||
isReal: boolean;
|
isReal: boolean;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
};
|
};
|
||||||
|
@ -46,7 +46,7 @@ limitations under the License. -->
|
|||||||
ref="multipleTableRef"
|
ref="multipleTableRef"
|
||||||
:default-sort="{ prop: 'name', order: 'ascending' }"
|
:default-sort="{ prop: 'name', order: 'ascending' }"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
height="637px"
|
height="calc(100% - 60px)"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
@ -156,7 +156,7 @@ import { isEmptyObject } from "@/utils/is";
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStoreWithOut();
|
const appStore = useAppStoreWithOut();
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const pageSize = 18;
|
const pageSize = 20;
|
||||||
const dashboards = ref<DashboardItem[]>([]);
|
const dashboards = ref<DashboardItem[]>([]);
|
||||||
const searchText = ref<string>("");
|
const searchText = ref<string>("");
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
@ -500,12 +500,13 @@ function changePage(pageIndex: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
padding: 20px;
|
padding: 20px 10px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0px 1px 4px 0px #00000029;
|
box-shadow: 0px 1px 4px 0px #00000029;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-selection {
|
.toggle-selection {
|
||||||
|
@ -228,6 +228,11 @@ export default defineComponent({
|
|||||||
watch(
|
watch(
|
||||||
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
|
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
|
||||||
() => {
|
() => {
|
||||||
|
if (
|
||||||
|
!(selectorStore.currentDestProcess && selectorStore.currentProcess)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (dashboardStore.entity === EntityType[7].value) {
|
if (dashboardStore.entity === EntityType[7].value) {
|
||||||
queryMetrics();
|
queryMetrics();
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,17 @@ limitations under the License. -->
|
|||||||
<div class="search">
|
<div class="search">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
placeholder="Please input endpoint name"
|
placeholder="Search for more endpoints"
|
||||||
size="small"
|
|
||||||
@change="searchList"
|
@change="searchList"
|
||||||
class="inputs"
|
class="inputs"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button size="small" @click="searchList">
|
<el-button @click="searchList" class="btn">
|
||||||
<Icon size="sm" iconName="search" />
|
<Icon size="middle" iconName="search" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<span class="ml-5 tips">{{ t("endpointTips") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%">
|
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%">
|
||||||
@ -56,6 +56,7 @@ limitations under the License. -->
|
|||||||
import { ref, watch, computed } from "vue";
|
import { ref, watch, computed } from "vue";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import { EndpointListConfig } from "@/types/dashboard";
|
import { EndpointListConfig } from "@/types/dashboard";
|
||||||
import { Endpoint } from "@/types/selector";
|
import { Endpoint } from "@/types/selector";
|
||||||
@ -92,6 +93,7 @@ const props = defineProps({
|
|||||||
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
|
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const selectorStore = useSelectorStore();
|
const selectorStore = useSelectorStore();
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const chartLoading = ref<boolean>(false);
|
const chartLoading = ref<boolean>(false);
|
||||||
@ -191,11 +193,7 @@ watch(
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./style.scss";
|
@import "./style.scss";
|
||||||
|
|
||||||
.chart {
|
.tips {
|
||||||
height: 60px;
|
color: rgba(255, 0, 0, 0.5);
|
||||||
}
|
|
||||||
|
|
||||||
.inputs {
|
|
||||||
width: 300px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -18,12 +18,11 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
placeholder="Please input instance name"
|
placeholder="Please input instance name"
|
||||||
size="small"
|
|
||||||
@change="searchList"
|
@change="searchList"
|
||||||
class="inputs"
|
class="inputs"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button size="small" @click="searchList">
|
<el-button class="btn" @click="searchList">
|
||||||
<Icon size="sm" iconName="search" />
|
<Icon size="sm" iconName="search" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -243,14 +242,6 @@ watch(
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./style.scss";
|
@import "./style.scss";
|
||||||
|
|
||||||
.chart {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes {
|
.attributes {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -18,12 +18,11 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
placeholder="Please input service name"
|
placeholder="Please input service name"
|
||||||
size="small"
|
|
||||||
@change="searchList"
|
@change="searchList"
|
||||||
class="inputs mt-5"
|
class="inputs mt-5"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button size="small" @click="searchList">
|
<el-button class="btn" @click="searchList">
|
||||||
<Icon size="sm" iconName="search" />
|
<Icon size="sm" iconName="search" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -286,8 +285,4 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./style.scss";
|
@import "./style.scss";
|
||||||
|
|
||||||
.inputs {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
.list {
|
.list {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
height: calc(100% - 90px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
@ -40,9 +41,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
text-align: right;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-with-search {
|
.input-with-search {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-top: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
@ -19,11 +19,11 @@ export default (d3: any, graph: any, diff: number[]) =>
|
|||||||
d3
|
d3
|
||||||
.zoom()
|
.zoom()
|
||||||
.scaleExtent([0.3, 10])
|
.scaleExtent([0.3, 10])
|
||||||
.on("zoom", (event: any) => {
|
.on("zoom", (d: any) => {
|
||||||
graph.attr(
|
graph.attr(
|
||||||
"transform",
|
"transform",
|
||||||
`translate(${event.transform.x + diff[0]},${
|
`translate(${d.transform.x + diff[0]},${
|
||||||
event.transform.y + diff[1]
|
d.transform.y + diff[1]
|
||||||
})scale(${event.transform.k})`
|
})scale(${d.transform.k})`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -53,7 +53,7 @@ const { t } = useI18n();
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
min-width: 700px;
|
min-width: 700px;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100% - 330px);
|
width: calc(100% - 330px);
|
||||||
}
|
}
|
||||||
|
@ -17,96 +17,6 @@
|
|||||||
import icons from "@/assets/img/icons";
|
import icons from "@/assets/img/icons";
|
||||||
import { Call } from "@/types/topology";
|
import { Call } from "@/types/topology";
|
||||||
|
|
||||||
export const linkElement = (graph: any) => {
|
|
||||||
const linkEnter = graph
|
|
||||||
.append("path")
|
|
||||||
.attr("class", "topo-call")
|
|
||||||
.attr("marker-end", "url(#arrow)")
|
|
||||||
.attr("stroke", "#97B0F8")
|
|
||||||
.attr("d", (d: Call) => {
|
|
||||||
const controlPos = computeControlPoint(
|
|
||||||
[d.source.x, d.source.y - 5],
|
|
||||||
[d.target.x, d.target.y - 5],
|
|
||||||
0.5
|
|
||||||
);
|
|
||||||
if (d.lowerArc) {
|
|
||||||
controlPos[1] =
|
|
||||||
Math.abs(controlPos[1]) < 50
|
|
||||||
? -controlPos[1] + 90
|
|
||||||
: -controlPos[1] - 10;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
"M" +
|
|
||||||
d.source.x +
|
|
||||||
" " +
|
|
||||||
(d.source.y - 5) +
|
|
||||||
" " +
|
|
||||||
"Q" +
|
|
||||||
controlPos[0] +
|
|
||||||
" " +
|
|
||||||
controlPos[1] +
|
|
||||||
" " +
|
|
||||||
d.target.x +
|
|
||||||
" " +
|
|
||||||
(d.target.y - 5)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return linkEnter;
|
|
||||||
};
|
|
||||||
export const anchorElement = (graph: any, funcs: any, tip: any) => {
|
|
||||||
const linkEnter = graph
|
|
||||||
.append("g")
|
|
||||||
.attr("class", "topo-line-anchor")
|
|
||||||
.on("mouseover", function (event: unknown, d: unknown) {
|
|
||||||
tip.html(funcs.tipHtml).show(d, this);
|
|
||||||
})
|
|
||||||
.on("mouseout", function () {
|
|
||||||
tip.hide(this);
|
|
||||||
})
|
|
||||||
.on("click", (event: unknown, d: unknown) => {
|
|
||||||
funcs.handleLinkClick(event, d);
|
|
||||||
});
|
|
||||||
linkEnter
|
|
||||||
.append("image")
|
|
||||||
.attr("width", 15)
|
|
||||||
.attr("height", 15)
|
|
||||||
.attr("x", (d: Call) => {
|
|
||||||
const p = getMidpoint(d);
|
|
||||||
return p[0] - 8;
|
|
||||||
})
|
|
||||||
.attr("y", (d: Call) => {
|
|
||||||
const p = getMidpoint(d);
|
|
||||||
return p[1] - 13;
|
|
||||||
})
|
|
||||||
.attr("xlink:href", (d: Call) => {
|
|
||||||
const types = [...d.sourceComponents, ...d.targetComponents];
|
|
||||||
if (types.includes("tcp") || types.includes("http")) {
|
|
||||||
return icons.HTTPDARK;
|
|
||||||
}
|
|
||||||
if (types.includes("https") || types.includes("tls")) {
|
|
||||||
return icons.HTTPS;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return linkEnter;
|
|
||||||
};
|
|
||||||
export const arrowMarker = (graph: any) => {
|
|
||||||
const defs = graph.append("defs");
|
|
||||||
const arrow = defs
|
|
||||||
.append("marker")
|
|
||||||
.attr("id", "arrow")
|
|
||||||
.attr("class", "topo-line-arrow")
|
|
||||||
.attr("markerUnits", "strokeWidth")
|
|
||||||
.attr("markerWidth", "8")
|
|
||||||
.attr("markerHeight", "8")
|
|
||||||
.attr("viewBox", "0 0 12 12")
|
|
||||||
.attr("refX", "10")
|
|
||||||
.attr("refY", "6")
|
|
||||||
.attr("orient", "auto");
|
|
||||||
const arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2";
|
|
||||||
|
|
||||||
arrow.append("path").attr("d", arrowPath).attr("fill", "#97B0F8");
|
|
||||||
return arrow;
|
|
||||||
};
|
|
||||||
// Control Point coordinates of quadratic Bezier curve
|
// Control Point coordinates of quadratic Bezier curve
|
||||||
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
|
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
|
||||||
const deltaX = pe[0] - ps[0];
|
const deltaX = pe[0] - ps[0];
|
||||||
@ -137,15 +47,20 @@ function quadraticBezier(
|
|||||||
const y = (1 - t) * (1 - t) * ps.y + 2 * t * (1 - t) * pc.y + t * t * pe.y;
|
const y = (1 - t) * (1 - t) * ps.y + 2 * t * (1 - t) * pc.y + t * t * pe.y;
|
||||||
return [x, y];
|
return [x, y];
|
||||||
}
|
}
|
||||||
function getMidpoint(d: Call) {
|
export function getMidpoint(d: Call) {
|
||||||
|
if (isNaN(d.source.x) || isNaN(d.source.y)) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
if (isNaN(d.target.x) || isNaN(d.target.y)) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
const controlPos = computeControlPoint(
|
const controlPos = computeControlPoint(
|
||||||
[d.source.x, d.source.y],
|
[d.source.x, d.source.y],
|
||||||
[d.target.x, d.target.y],
|
[d.target.x, d.target.y],
|
||||||
0.5
|
0.5
|
||||||
);
|
);
|
||||||
if (d.lowerArc) {
|
if (d.lowerArc) {
|
||||||
controlPos[1] =
|
controlPos[1] = -controlPos[1];
|
||||||
Math.abs(controlPos[1]) < 50 ? -controlPos[1] + 100 : -controlPos[1] - 10;
|
|
||||||
}
|
}
|
||||||
const p = quadraticBezier(
|
const p = quadraticBezier(
|
||||||
0.5,
|
0.5,
|
||||||
@ -155,3 +70,43 @@ function getMidpoint(d: Call) {
|
|||||||
);
|
);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
export function linkPath(d: Call) {
|
||||||
|
if (isNaN(d.source.x) || isNaN(d.source.y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNaN(d.target.x) || isNaN(d.target.y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const controlPos = computeControlPoint(
|
||||||
|
[d.source.x, d.source.y - 5],
|
||||||
|
[d.target.x, d.target.y - 5],
|
||||||
|
0.5
|
||||||
|
);
|
||||||
|
if (d.lowerArc) {
|
||||||
|
controlPos[1] = -controlPos[1] - 10;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
"M" +
|
||||||
|
d.source.x +
|
||||||
|
" " +
|
||||||
|
(d.source.y - 5) +
|
||||||
|
" " +
|
||||||
|
"Q" +
|
||||||
|
controlPos[0] +
|
||||||
|
" " +
|
||||||
|
controlPos[1] +
|
||||||
|
" " +
|
||||||
|
d.target.x +
|
||||||
|
" " +
|
||||||
|
(d.target.y - 5)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function getAnchor(d: Call) {
|
||||||
|
const types = [...d.sourceComponents, ...d.targetComponents];
|
||||||
|
if (types.includes("tcp") || types.includes("http")) {
|
||||||
|
return icons.HTTPDARK;
|
||||||
|
}
|
||||||
|
if (types.includes("https") || types.includes("tls")) {
|
||||||
|
return icons.HTTPS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,54 +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";
|
|
||||||
|
|
||||||
export default (d3: any, graph: any, funcs: any, tip: any) => {
|
|
||||||
const nodeEnter = graph
|
|
||||||
.append("g")
|
|
||||||
.call(
|
|
||||||
d3
|
|
||||||
.drag()
|
|
||||||
.on("start", funcs.dragstart)
|
|
||||||
.on("drag", funcs.dragged)
|
|
||||||
.on("end", funcs.dragended)
|
|
||||||
)
|
|
||||||
.on("mouseover", function (event: unknown, d: Node) {
|
|
||||||
tip.html(funcs.tipHtml).show(d, this);
|
|
||||||
})
|
|
||||||
.on("mouseout", function () {
|
|
||||||
tip.hide(this);
|
|
||||||
});
|
|
||||||
nodeEnter
|
|
||||||
.append("image")
|
|
||||||
.attr("width", 35)
|
|
||||||
.attr("height", 35)
|
|
||||||
.attr("x", (d: any) => d.x - 15)
|
|
||||||
.attr("y", (d: any) => d.y - 15)
|
|
||||||
.attr("style", "cursor: move;")
|
|
||||||
.attr("xlink:href", icons.CUBE);
|
|
||||||
nodeEnter
|
|
||||||
.append("text")
|
|
||||||
.attr("fill", "#000")
|
|
||||||
.attr("text-anchor", "middle")
|
|
||||||
.attr("x", (d: any) => d.x)
|
|
||||||
.attr("y", (d: any) => d.y + 28)
|
|
||||||
.text((d: { name: string }) =>
|
|
||||||
d.name.length > 10 ? `${d.name.substring(0, 10)}...` : d.name
|
|
||||||
);
|
|
||||||
return nodeEnter;
|
|
||||||
};
|
|
@ -13,13 +13,103 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<template>
|
<template>
|
||||||
<div ref="chart" class="process-topo"></div>
|
<div ref="chart" class="process-topo">
|
||||||
<el-popover
|
<svg
|
||||||
placement="bottom"
|
class="process-svg"
|
||||||
:width="295"
|
:width="width"
|
||||||
trigger="click"
|
:height="height"
|
||||||
v-if="dashboardStore.editMode"
|
@click="clickTopology"
|
||||||
>
|
>
|
||||||
|
<g class="svg-graph" :transform="`translate(${diff[0]}, ${diff[1]})`">
|
||||||
|
<g class="hex-polygon">
|
||||||
|
<path
|
||||||
|
:d="getHexPolygonVertices()"
|
||||||
|
stroke="#D5DDF6"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<text :x="0" :y="radius - 15" fill="#000" text-anchor="middle">
|
||||||
|
{{ selectorStore.currentPod.label }}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g class="nodes">
|
||||||
|
<g
|
||||||
|
v-for="(node, index) in nodeList"
|
||||||
|
:key="index"
|
||||||
|
class="node"
|
||||||
|
@mouseover="showNodeTip(node, $event)"
|
||||||
|
@mouseout="hideNodeTip"
|
||||||
|
@mousedown="startMoveNode($event, node)"
|
||||||
|
@mouseup="stopMoveNode($event)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:href="icons.CUBE"
|
||||||
|
style="cursor: 'move'"
|
||||||
|
width="35"
|
||||||
|
height="35"
|
||||||
|
:x="(node.x || 0) - 15"
|
||||||
|
:y="(node.y || 0) - 15"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
:x="node.x"
|
||||||
|
:y="(node.y || 0) + 28"
|
||||||
|
fill="#000"
|
||||||
|
text-anchor="middle"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
node.name.length > 10
|
||||||
|
? `${node.name.substring(0, 10)}...`
|
||||||
|
: node.name
|
||||||
|
}}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="calls">
|
||||||
|
<path
|
||||||
|
v-for="(call, index) in networkProfilingStore.calls"
|
||||||
|
:key="index"
|
||||||
|
class="topo-call"
|
||||||
|
marker-end="url(#arrow)"
|
||||||
|
stroke="#97B0F8"
|
||||||
|
:d="linkPath(call)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g class="anchors">
|
||||||
|
<image
|
||||||
|
v-for="(call, index) in networkProfilingStore.calls"
|
||||||
|
:key="index"
|
||||||
|
class="topo-line-anchor"
|
||||||
|
:href="getAnchor(call)"
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
:x="getMidpoint(call)[0] - 8"
|
||||||
|
:y="getMidpoint(call)[1] - 13"
|
||||||
|
@click="handleLinkClick($event, call)"
|
||||||
|
@mouseover="showLinkTip(call, $event)"
|
||||||
|
@mouseout="hideLinkTip"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g class="arrows">
|
||||||
|
<defs v-for="(_, index) in networkProfilingStore.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="#97B0F8" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div id="tooltip"></div>
|
||||||
|
</div>
|
||||||
|
<el-popover placement="bottom" :width="295" trigger="click">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div class="switch-icon-edit ml-5" title="Settings" @click="setConfig">
|
<div class="switch-icon-edit ml-5" title="Settings" @click="setConfig">
|
||||||
<Icon size="middle" iconName="setting_empty" />
|
<Icon size="middle" iconName="setting_empty" />
|
||||||
@ -39,9 +129,7 @@ import router from "@/router";
|
|||||||
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import d3tip from "d3-tip";
|
import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
|
||||||
import { linkElement, anchorElement, arrowMarker } from "./Graph/linkProcess";
|
|
||||||
import nodeElement from "./Graph/nodeProcess";
|
|
||||||
import { Call } from "@/types/topology";
|
import { Call } from "@/types/topology";
|
||||||
import zoom from "../../components/utils/zoom";
|
import zoom from "../../components/utils/zoom";
|
||||||
import { ProcessNode } from "@/types/ebpf";
|
import { ProcessNode } from "@/types/ebpf";
|
||||||
@ -52,6 +140,7 @@ import getDashboard from "@/hooks/useDashboardsSession";
|
|||||||
import { Layout } from "./Graph/layout";
|
import { Layout } from "./Graph/layout";
|
||||||
import TimeLine from "./TimeLine.vue";
|
import TimeLine from "./TimeLine.vue";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
import icons from "@/assets/img/icons";
|
||||||
|
|
||||||
/*global Nullable, defineProps */
|
/*global Nullable, defineProps */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -67,19 +156,18 @@ const selectorStore = useSelectorStore();
|
|||||||
const networkProfilingStore = useNetworkProfilingStore();
|
const networkProfilingStore = useNetworkProfilingStore();
|
||||||
const height = ref<number>(100);
|
const height = ref<number>(100);
|
||||||
const width = ref<number>(100);
|
const width = ref<number>(100);
|
||||||
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 tooltip = ref<Nullable<any>>(null);
|
||||||
const graph = ref<any>(null);
|
const svg = ref<Nullable<any>>(null);
|
||||||
const node = ref<any>(null);
|
const graph = ref<Nullable<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 });
|
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
|
||||||
const config = ref<any>(props.config || {});
|
const config = ref<any>(props.config || {});
|
||||||
const diff = ref<number[]>([220, 200]);
|
const diff = ref<number[]>([220, 200]);
|
||||||
const radius = 210;
|
const radius = 210;
|
||||||
const dates = ref<Nullable<{ start: number; end: number }>>(null);
|
const dates = ref<Nullable<{ start: number; end: number }>>(null);
|
||||||
|
const nodeList = ref<ProcessNode[]>([]);
|
||||||
|
const currentNode = ref<Nullable<ProcessNode>>(null);
|
||||||
|
const origin = [0, 0];
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init();
|
init();
|
||||||
@ -90,12 +178,14 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
svg.value = d3.select(chart.value).append("svg").attr("class", "process-svg");
|
|
||||||
if (!networkProfilingStore.nodes.length) {
|
if (!networkProfilingStore.nodes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
drawGraph();
|
svg.value = d3.select(".process-svg");
|
||||||
createLayout();
|
graph.value = d3.select(".svg-graph");
|
||||||
|
tooltip.value = d3.select("#tooltip");
|
||||||
|
freshNodes();
|
||||||
|
useThrottleFn(resize, 500)();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawGraph() {
|
function drawGraph() {
|
||||||
@ -105,27 +195,16 @@ function drawGraph() {
|
|||||||
};
|
};
|
||||||
height.value = (dom.height || 40) - 20;
|
height.value = (dom.height || 40) - 20;
|
||||||
width.value = dom.width;
|
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]);
|
|
||||||
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
|
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
|
||||||
graph.value = svg.value
|
|
||||||
.append("g")
|
|
||||||
.attr("class", "svg-graph")
|
|
||||||
.attr("transform", `translate(${diff.value[0]}, ${diff.value[1]})`);
|
|
||||||
graph.value.call(tip.value);
|
|
||||||
node.value = graph.value.append("g").selectAll(".topo-node");
|
|
||||||
link.value = graph.value.append("g").selectAll(".topo-call");
|
|
||||||
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, diff.value));
|
svg.value.call(zoom(d3, graph.value, diff.value));
|
||||||
svg.value.on("click", (event: any) => {
|
}
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
function clickTopology(event: MouseEvent) {
|
||||||
networkProfilingStore.setNode(null);
|
event.stopPropagation();
|
||||||
networkProfilingStore.setLink(null);
|
event.preventDefault();
|
||||||
dashboardStore.selectWidget(props.config);
|
networkProfilingStore.setNode(null);
|
||||||
});
|
networkProfilingStore.setLink(null);
|
||||||
useThrottleFn(resize, 500)();
|
dashboardStore.selectWidget(props.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
|
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
|
||||||
@ -157,7 +236,6 @@ function createPolygon(radius: number, sides = 6, offset = 0) {
|
|||||||
}
|
}
|
||||||
function getCirclePoint(radius: number, p = 1) {
|
function getCirclePoint(radius: number, p = 1) {
|
||||||
const data = [];
|
const data = [];
|
||||||
const origin = [0, 0];
|
|
||||||
for (let index = 0; index < 360; index = index + p) {
|
for (let index = 0; index < 360; index = index + p) {
|
||||||
if (index < 230 || index > 310) {
|
if (index < 230 || index > 310) {
|
||||||
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
|
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
|
||||||
@ -167,10 +245,22 @@ function getCirclePoint(radius: number, p = 1) {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
function createLayout() {
|
|
||||||
if (!node.value || !link.value) {
|
function getHexPolygonVertices() {
|
||||||
return;
|
const p = {
|
||||||
|
count: 1,
|
||||||
|
radius, // layout hexagons radius 300
|
||||||
|
};
|
||||||
|
const polygon = createPolygon(p.radius, 6, 0);
|
||||||
|
const vertices: any = []; // a hexagon vertices
|
||||||
|
for (let v = 0; v < polygon.length; v++) {
|
||||||
|
vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]);
|
||||||
}
|
}
|
||||||
|
const linePath = d3.line();
|
||||||
|
linePath.curve(d3.curveLinearClosed);
|
||||||
|
return linePath(vertices) || "";
|
||||||
|
}
|
||||||
|
function createLayout() {
|
||||||
const dom: any = (chart.value && chart.value.getBoundingClientRect()) || {
|
const dom: any = (chart.value && chart.value.getBoundingClientRect()) || {
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
@ -182,28 +272,6 @@ function createLayout() {
|
|||||||
count: 1,
|
count: 1,
|
||||||
radius, // layout hexagons radius 300
|
radius, // layout hexagons radius 300
|
||||||
};
|
};
|
||||||
const polygon = createPolygon(p.radius, 6, 0);
|
|
||||||
const origin = [0, 0];
|
|
||||||
const vertices: any = []; // a hexagon vertices
|
|
||||||
for (let v = 0; v < polygon.length; v++) {
|
|
||||||
vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]);
|
|
||||||
}
|
|
||||||
const linePath = d3.line();
|
|
||||||
linePath.curve(d3.curveLinearClosed);
|
|
||||||
const hexPolygon = graph.value.append("g");
|
|
||||||
hexPolygon
|
|
||||||
.append("path")
|
|
||||||
.attr("d", linePath(vertices))
|
|
||||||
.attr("stroke", "#D5DDF6")
|
|
||||||
.attr("stroke-width", 2)
|
|
||||||
.style("fill", "none");
|
|
||||||
hexPolygon
|
|
||||||
.append("text")
|
|
||||||
.attr("fill", "#000")
|
|
||||||
.attr("text-anchor", "middle")
|
|
||||||
.attr("x", 0)
|
|
||||||
.attr("y", p.radius - 15)
|
|
||||||
.text(() => selectorStore.currentPod.label);
|
|
||||||
const nodeArr = networkProfilingStore.nodes.filter(
|
const nodeArr = networkProfilingStore.nodes.filter(
|
||||||
(d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL"
|
(d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL"
|
||||||
);
|
);
|
||||||
@ -278,67 +346,11 @@ function createLayout() {
|
|||||||
outNodes[v].x = pointArr[v][0];
|
outNodes[v].x = pointArr[v][0];
|
||||||
outNodes[v].y = pointArr[v][1];
|
outNodes[v].y = pointArr[v][1];
|
||||||
}
|
}
|
||||||
drawTopology([...nodeArr, ...outNodes]);
|
nodeList.value = [...nodeArr, ...outNodes];
|
||||||
}
|
const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
|
||||||
|
moveNode(d);
|
||||||
function drawTopology(nodeArr: any[]) {
|
});
|
||||||
node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id);
|
d3.selectAll(".node").call(drag);
|
||||||
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
|
|
||||||
const obj = {} as any;
|
|
||||||
const calls = networkProfilingStore.calls.reduce((prev: any[], next: any) => {
|
|
||||||
if (obj[next.targetId + next.sourceId]) {
|
|
||||||
next.lowerArc = true;
|
|
||||||
}
|
|
||||||
obj[next.sourceId + next.targetId] = true;
|
|
||||||
prev.push(next);
|
|
||||||
return prev;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
link.value = link.value.data(calls, (d: Call) => d.id);
|
|
||||||
link.value.exit().remove();
|
|
||||||
link.value = linkElement(link.value.enter()).merge(link.value);
|
|
||||||
anchor.value = anchor.value.data(calls, (d: Call) => d.id);
|
|
||||||
anchor.value.exit().remove();
|
|
||||||
anchor.value = anchorElement(
|
|
||||||
anchor.value.enter(),
|
|
||||||
{
|
|
||||||
handleLinkClick: handleLinkClick,
|
|
||||||
tipHtml: (data: Call) => {
|
|
||||||
const types = [...data.sourceComponents, ...data.targetComponents];
|
|
||||||
let l = "TCP";
|
|
||||||
if (types.includes("https")) {
|
|
||||||
l = "HTTPS";
|
|
||||||
}
|
|
||||||
if (types.includes("http")) {
|
|
||||||
l = "HTTP";
|
|
||||||
}
|
|
||||||
if (types.includes("tls")) {
|
|
||||||
l = "TLS";
|
|
||||||
}
|
|
||||||
const html = `<div><span class="grey">${t(
|
|
||||||
"detectPoint"
|
|
||||||
)}: </span>${data.detectPoints.join(" | ")}</div>
|
|
||||||
<div><span class="grey">Type: </span>${l}</div>`;
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tip.value
|
|
||||||
).merge(anchor.value);
|
|
||||||
// arrow marker
|
|
||||||
arrow.value = arrow.value.data(calls, (d: Call) => d.id);
|
|
||||||
arrow.value.exit().remove();
|
|
||||||
arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffleArray(array: number[][]) {
|
function shuffleArray(array: number[][]) {
|
||||||
@ -347,7 +359,6 @@ function shuffleArray(array: number[][]) {
|
|||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLinkClick(event: any, d: Call) {
|
function handleLinkClick(event: any, d: Call) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
networkProfilingStore.setNode(null);
|
networkProfilingStore.setNode(null);
|
||||||
@ -423,13 +434,100 @@ function resize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function freshNodes() {
|
async function freshNodes() {
|
||||||
svg.value.selectAll(".svg-graph").remove();
|
|
||||||
if (!networkProfilingStore.nodes.length) {
|
if (!networkProfilingStore.nodes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
drawGraph();
|
drawGraph();
|
||||||
createLayout();
|
createLayout();
|
||||||
}
|
}
|
||||||
|
function startMoveNode(event: MouseEvent, d: ProcessNode) {
|
||||||
|
event.stopPropagation();
|
||||||
|
currentNode.value = d;
|
||||||
|
}
|
||||||
|
function stopMoveNode(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
currentNode.value = null;
|
||||||
|
}
|
||||||
|
function moveNode(d: ProcessNode) {
|
||||||
|
if (!currentNode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inNode =
|
||||||
|
currentNode.value.isReal || currentNode.value.name === "UNKNOWN_LOCAL";
|
||||||
|
const diff = inNode ? -20 : 20;
|
||||||
|
const inside = posInHex(d.x || 0, d.y || 0, diff);
|
||||||
|
if (inNode) {
|
||||||
|
if (!inside) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (inside) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeList.value = nodeList.value.map((node: ProcessNode) => {
|
||||||
|
if (currentNode.value && node.id === currentNode.value.id) {
|
||||||
|
node.x = d.x;
|
||||||
|
node.y = d.y;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function posInHex(posX: number, posY: number, diff: number) {
|
||||||
|
const halfSideLen = (radius + diff) / 2;
|
||||||
|
const mathSqrt3 = Math.sqrt(3);
|
||||||
|
const dx = Math.abs(origin[0] - posX);
|
||||||
|
const dy = Math.abs(origin[1] - posY);
|
||||||
|
|
||||||
|
if (dx < halfSideLen) {
|
||||||
|
return dy <= halfSideLen * mathSqrt3;
|
||||||
|
} else {
|
||||||
|
const maxY = -mathSqrt3 * (dx - halfSideLen) + halfSideLen * mathSqrt3;
|
||||||
|
return dy < maxY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNodeTip(d: ProcessNode, event: MouseEvent) {
|
||||||
|
const tipHtml = ` <div class="mb-5"><span class="grey">name: </span>${d.name}</div>`;
|
||||||
|
|
||||||
|
tooltip.value
|
||||||
|
.style("top", event.offsetY + "px")
|
||||||
|
.style("left", event.offsetX + "px")
|
||||||
|
.style("visibility", "visible")
|
||||||
|
.html(tipHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideNodeTip() {
|
||||||
|
tooltip.value.style("visibility", "hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLinkTip(link: Call, event: MouseEvent) {
|
||||||
|
const types = [...link.sourceComponents, ...link.targetComponents];
|
||||||
|
let l = "TCP";
|
||||||
|
if (types.includes("https")) {
|
||||||
|
l = "HTTPS";
|
||||||
|
}
|
||||||
|
if (types.includes("http")) {
|
||||||
|
l = "HTTP";
|
||||||
|
}
|
||||||
|
if (types.includes("tls")) {
|
||||||
|
l = "TLS";
|
||||||
|
}
|
||||||
|
const tipHtml = `<div><span class="grey">${t(
|
||||||
|
"detectPoint"
|
||||||
|
)}: </span>${link.detectPoints.join(" | ")}</div>
|
||||||
|
<div><span class="grey">Type: </span>${l}</div>`;
|
||||||
|
|
||||||
|
tooltip.value
|
||||||
|
.style("top", event.offsetY + "px")
|
||||||
|
.style("left", event.offsetX + "px")
|
||||||
|
.style("visibility", "visible")
|
||||||
|
.html(tipHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLinkTip() {
|
||||||
|
tooltip.value.style("visibility", "hidden");
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => networkProfilingStore.nodes,
|
() => networkProfilingStore.nodes,
|
||||||
@ -500,4 +598,13 @@ watch(
|
|||||||
.query {
|
.query {
|
||||||
margin-left: 510px;
|
margin-left: 510px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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