build: migrate the build tool from vue-cli to vite4 (#208)

This commit is contained in:
Fine0830
2022-12-17 14:07:03 +08:00
committed by GitHub
parent 1e0c253488
commit 44dcb1e7f6
214 changed files with 27014 additions and 54234 deletions

View File

@@ -16,10 +16,7 @@ limitations under the License. -->
<div class="flex-h content">
<Tasks />
<div class="vis-graph ml-5" v-loading="networkProfilingStore.loadNodes">
<process-topology
v-if="networkProfilingStore.nodes.length"
:config="config"
/>
<process-topology v-if="networkProfilingStore.nodes.length" :config="config" />
<div class="text" v-else>
{{ t("noData") }}
</div>
@@ -27,40 +24,40 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import Tasks from "./components/Tasks.vue";
import ProcessTopology from "./components/ProcessTopology.vue";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import Tasks from "./components/Tasks.vue";
import ProcessTopology from "./components/ProcessTopology.vue";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const networkProfilingStore = useNetworkProfilingStore();
const { t } = useI18n();
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const networkProfilingStore = useNetworkProfilingStore();
const { t } = useI18n();
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.content {
height: calc(100% - 30px);
width: 100%;
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
width: calc(100% - 330px);
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
width: calc(100% - 330px);
}
.text {
width: 100%;
text-align: center;
margin-top: 30px;
}
.text {
width: 100%;
text-align: center;
margin-top: 30px;
}
</style>

View File

@@ -33,7 +33,7 @@ class Orientation {
b1: number,
b2: number,
b3: number,
start_angle: number
start_angle: number,
) {
this.f0 = f0;
this.f1 = f1;
@@ -49,33 +49,11 @@ class Orientation {
const SQRT3 = Math.sqrt(3.0);
class Layout {
static Pointy = new Orientation(
SQRT3,
SQRT3 / 2.0,
0.0,
3.0 / 2.0,
SQRT3 / 3.0,
-1.0 / 3.0,
0.0,
2.0 / 3.0,
0.5
);
static Flat = new Orientation(
3.0 / 2.0,
0.0,
SQRT3 / 2.0,
SQRT3,
2.0 / 3.0,
0.0,
-1.0 / 3.0,
SQRT3 / 3.0,
0.0
);
static Pointy = new Orientation(SQRT3, SQRT3 / 2.0, 0.0, 3.0 / 2.0, SQRT3 / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5);
static Flat = new Orientation(3.0 / 2.0, 0.0, SQRT3 / 2.0, SQRT3, 2.0 / 3.0, 0.0, -1.0 / 3.0, SQRT3 / 3.0, 0.0);
static spacing(radius: number, isPointy = false): number[] {
return isPointy
? [SQRT3 * radius, 2 * radius * (3 / 4)]
: [2 * radius * (3 / 4), SQRT3 * radius];
return isPointy ? [SQRT3 * radius, 2 * radius * (3 / 4)] : [2 * radius * (3 / 4), SQRT3 * radius];
}
private radius = 1;
@@ -135,9 +113,7 @@ class Hex extends Int16Array {
}
get len(): number {
return Math.floor(
(Math.abs(this[0]) + Math.abs(this[1]) + Math.abs(this[2])) / 2
);
return Math.floor((Math.abs(this[0]) + Math.abs(this[1]) + Math.abs(this[2])) / 2);
}
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import icons from "@/assets/img/icons";
import { Call } from "@/types/topology";
import type { Call } from "@/types/topology";
// Control Point coordinates of quadratic Bezier curve
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
@@ -24,10 +24,7 @@ function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
const theta = Math.atan(deltaY / deltaX);
const len = (Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2) * arc;
const newTheta = theta - Math.PI / 2;
return [
(ps[0] + pe[0]) / 2 + len * Math.cos(newTheta),
(ps[1] + pe[1]) / 2 + len * Math.sin(newTheta),
];
return [(ps[0] + pe[0]) / 2 + len * Math.cos(newTheta), (ps[1] + pe[1]) / 2 + len * Math.sin(newTheta)];
}
// Point coordinates of quadratic Bezier curve
/**
@@ -41,7 +38,7 @@ function quadraticBezier(
t: number,
ps: { x: number; y: number },
pc: { x: number; y: number },
pe: { x: number; y: number }
pe: { x: number; y: number },
) {
const x = (1 - t) * (1 - t) * ps.x + 2 * t * (1 - t) * pc.x + t * t * pe.x;
const y = (1 - t) * (1 - t) * ps.y + 2 * t * (1 - t) * pc.y + t * t * pe.y;
@@ -54,11 +51,7 @@ export function getMidpoint(d: Call) {
if (isNaN(d.target.x) || isNaN(d.target.y)) {
return [0, 0];
}
const controlPos = computeControlPoint(
[d.source.x, d.source.y],
[d.target.x, d.target.y],
0.5
);
const controlPos = computeControlPoint([d.source.x, d.source.y], [d.target.x, d.target.y], 0.5);
if (d.lowerArc) {
controlPos[1] = -controlPos[1];
}
@@ -66,7 +59,7 @@ export function getMidpoint(d: Call) {
0.5,
{ x: d.source.x, y: d.source.y },
{ x: controlPos[0], y: controlPos[1] },
{ x: d.target.x, y: d.target.y }
{ x: d.target.x, y: d.target.y },
);
return p;
}
@@ -77,11 +70,7 @@ export function linkPath(d: Call) {
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
);
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;
}

View File

@@ -21,76 +21,60 @@ limitations under the License. -->
</div>
<div>
<div class="label">{{ t("minDuration") }} (ms)</div>
<el-input
size="small"
class="profile-input"
:min="0"
v-model="states.minDuration"
type="number"
/>
<el-input size="small" class="profile-input" :min="0" v-model="states.minDuration" type="number" />
</div>
<div>
<div class="label">{{ t("when4xx") }}</div>
<Radio
class="mb-5"
:value="states.when4xx"
:options="InitTaskField.Whenxx"
@change="changeWhen4xx"
/>
<Radio class="mb-5" :value="states.when4xx" :options="InitTaskField.Whenxx" @change="changeWhen4xx" />
</div>
<div>
<div class="label">{{ t("when5xx") }}</div>
<Radio
class="mb-5"
:value="states.when5xx"
:options="InitTaskField.Whenxx"
@change="changeWhen5xx"
/>
<Radio class="mb-5" :value="states.when5xx" :options="InitTaskField.Whenxx" @change="changeWhen5xx" />
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { InitTaskField } from "./data";
import { NetworkProfilingRequest } from "@/types/ebpf";
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { InitTaskField } from "./data";
import type { NetworkProfilingRequest } from "@/types/ebpf";
/* global defineProps, defineEmits */
const emits = defineEmits(["change"]);
const props = defineProps({
condition: {
type: Object as PropType<NetworkProfilingRequest>,
default: () => ({ children: [] }),
},
key: {
type: Number,
default: () => 0,
},
});
const { t } = useI18n();
const states = reactive<NetworkProfilingRequest>(props.condition);
/* global defineEmits, defineProps */
const emits = defineEmits(["change"]);
const props = defineProps({
condition: {
type: Object as PropType<NetworkProfilingRequest>,
default: () => ({ children: [] }),
},
index: {
type: Number,
default: () => 0,
},
});
const { t } = useI18n();
const states = reactive<NetworkProfilingRequest>(props.condition);
function changeWhen5xx(value: string) {
states.when5xx = value;
emits("change", states, props.key);
}
function changeWhen4xx(value: string) {
states.when4xx = value;
emits("change", states, props.key);
}
function changeWhen5xx(value: string) {
states.when5xx = value;
emits("change", states, props.index);
}
function changeWhen4xx(value: string) {
states.when4xx = value;
emits("change", states, props.index);
}
</script>
<style lang="scss" scoped>
.date {
font-size: 12px;
}
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
.profile-input {
width: 300px;
}
</style>

View File

@@ -16,11 +16,7 @@ limitations under the License. -->
<template>
<div class="profile-task">
<el-collapse v-model="activeNames">
<el-collapse-item
v-for="(item, index) in conditionsList"
:key="index"
:name="index"
>
<el-collapse-item v-for="(item, index) in conditionsList" :key="index" :name="index">
<template #title>
<div>
<span class="title">{{ `Rule - ${index + 1}` }}</span>
@@ -40,12 +36,7 @@ limitations under the License. -->
/>
</div>
</template>
<NewCondition
:name="index"
:condition="item"
:key="index"
@change="changeConfig"
/>
<NewCondition :name="index" :condition="item" :index="index" @change="changeConfig" />
</el-collapse-item>
</el-collapse>
<div>
@@ -56,84 +47,77 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { InitTaskField } from "./data";
import NewCondition from "./NewConditions.vue";
import { NetworkProfilingRequest } from "@/types/ebpf";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { InitTaskField } from "./data";
import NewCondition from "./NewConditions.vue";
import type { NetworkProfilingRequest } from "@/types/ebpf";
/* global defineEmits */
const emits = defineEmits(["create"]);
const { t } = useI18n();
const activeNames = ref([0]);
const conditionsList = ref<NetworkProfilingRequest[]>([
{
uriRegex: "",
when4xx: InitTaskField.Whenxx[1].value,
when5xx: InitTaskField.Whenxx[0].value,
minDuration: NaN,
},
]);
/* global defineEmits */
const emits = defineEmits(["create"]);
const { t } = useI18n();
const activeNames = ref([0]);
const conditionsList = ref<NetworkProfilingRequest[]>([
{
uriRegex: "",
when4xx: InitTaskField.Whenxx[1].value,
when5xx: InitTaskField.Whenxx[0].value,
minDuration: NaN,
},
]);
function changeConfig(
params: { [key: string]: number | string },
index: number
) {
const key: string = Object.keys(params)[0];
(conditionsList.value[index] as any)[key] = params[key];
}
function createTask() {
const list = conditionsList.value.map((d: NetworkProfilingRequest) => {
return {
uriRegex: d.uriRegex || undefined,
when4xx: d.when4xx === InitTaskField.Whenxx[0].value ? true : false,
when5xx: d.when5xx === InitTaskField.Whenxx[0].value ? true : false,
minDuration: isNaN(Number(d.minDuration))
? undefined
: Number(d.minDuration),
settings: {
requireCompleteRequest: true,
requireCompleteResponse: true,
},
};
});
emits("create", list);
}
function createConditions(e: any) {
e.stopPropagation();
conditionsList.value.push({
uriRegex: "",
when4xx: InitTaskField.Whenxx[0].value,
when5xx: InitTaskField.Whenxx[1].value,
minDuration: NaN,
});
activeNames.value = [conditionsList.value.length - 1];
}
function removeConditions(e: any, key: number) {
e.stopPropagation();
if (conditionsList.value.length === 1) {
return;
function changeConfig(params: { [key: string]: number | string }, index: number) {
const key: string = Object.keys(params)[0];
(conditionsList.value[index] as any)[key] = params[key];
}
function createTask() {
const list = conditionsList.value.map((d: NetworkProfilingRequest) => {
return {
uriRegex: d.uriRegex || undefined,
when4xx: d.when4xx === InitTaskField.Whenxx[0].value ? true : false,
when5xx: d.when5xx === InitTaskField.Whenxx[0].value ? true : false,
minDuration: isNaN(Number(d.minDuration)) ? undefined : Number(d.minDuration),
settings: {
requireCompleteRequest: true,
requireCompleteResponse: true,
},
};
});
emits("create", list);
}
function createConditions(e: any) {
e.stopPropagation();
conditionsList.value.push({
uriRegex: "",
when4xx: InitTaskField.Whenxx[0].value,
when5xx: InitTaskField.Whenxx[1].value,
minDuration: NaN,
});
activeNames.value = [conditionsList.value.length - 1];
}
function removeConditions(e: any, key: number) {
e.stopPropagation();
if (conditionsList.value.length === 1) {
return;
}
conditionsList.value = conditionsList.value.filter((_, index: number) => index !== key);
}
conditionsList.value = conditionsList.value.filter(
(_, index: number) => index !== key
);
}
</script>
<style lang="scss" scoped>
.profile-task {
width: 100%;
}
.profile-task {
width: 100%;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.title {
display: inline-block;
margin-right: 5px;
}
.title {
display: inline-block;
margin-right: 5px;
}
</style>

View File

@@ -14,20 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div ref="chart" class="process-topo">
<svg
class="process-svg"
:width="width"
:height="height"
@click="clickTopology"
>
<svg class="process-svg" :width="width" :height="height" @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"
/>
<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>
@@ -50,17 +40,8 @@ limitations under the License. -->
: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 :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>
@@ -120,491 +101,473 @@ limitations under the License. -->
<TimeLine @get="getDates" />
</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 { ElMessage } from "element-plus";
import router from "@/router";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
import { Call } from "@/types/topology";
import zoom from "../../components/utils/zoom";
import { ProcessNode } from "@/types/ebpf";
import { useThrottleFn } from "@vueuse/core";
import Settings from "./Settings.vue";
import { EntityType } from "@/views/dashboard/data";
import getDashboard from "@/hooks/useDashboardsSession";
import { Layout } from "./Graph/layout";
import TimeLine from "./TimeLine.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import icons from "@/assets/img/icons";
import type { PropType } from "vue";
import { ref, onMounted, watch } from "vue";
import * as d3 from "d3";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import router from "@/router";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
import type { Call } from "@/types/topology";
import zoom from "../../components/utils/zoom";
import type { ProcessNode } from "@/types/ebpf";
import { useThrottleFn } from "@vueuse/core";
import Settings from "./Settings.vue";
import { EntityType } from "@/views/dashboard/data";
import getDashboard from "@/hooks/useDashboardsSession";
import { Layout } from "./Graph/layout";
import TimeLine from "./TimeLine.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import icons from "@/assets/img/icons";
/*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const height = ref<number>(100);
const width = ref<number>(100);
const chart = ref<Nullable<HTMLDivElement>>(null);
const tooltip = ref<Nullable<any>>(null);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const config = ref<any>(props.config || {});
const diff = ref<number[]>([220, 200]);
const radius = 210;
const dates = ref<Nullable<{ start: number; end: number }>>(null);
const nodeList = ref<ProcessNode[]>([]);
const currentNode = ref<Nullable<ProcessNode>>(null);
const origin = [0, 0];
onMounted(() => {
init();
oldVal.value = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
});
async function init() {
if (!networkProfilingStore.nodes.length) {
return;
}
svg.value = d3.select(".process-svg");
graph.value = d3.select(".svg-graph");
tooltip.value = d3.select("#tooltip");
freshNodes();
useThrottleFn(resize, 500)();
}
function drawGraph() {
const dom = chart.value?.getBoundingClientRect() || {
height: 20,
width: 0,
};
height.value = (dom.height || 40) - 20;
width.value = dom.width;
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
svg.value.call(zoom(d3, graph.value, diff.value));
}
function clickTopology(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
let x, y, yn, p;
const gLayout = new Layout(radius, origin);
const pos = [];
for (x = -n; x <= n; x++) {
y = Math.max(-n, -x - n); // 0
yn = Math.min(n, -x + n); // 1
for (y; y <= yn; y++) {
p = gLayout.axialToPixel(x, y);
pos.push(p);
}
}
return pos;
}
function createPolygon(radius: number, sides = 6, offset = 0) {
const poly: number[][] = [];
let i, rad;
for (i = 0; i < sides; i++) {
rad = Math.PI * 2 * (i / sides);
poly.push([
Math.cos(rad + offset) * radius,
Math.sin(rad + offset) * radius,
]);
}
return poly;
}
function getCirclePoint(radius: number, p = 1) {
const data = [];
for (let index = 0; index < 360; index = index + p) {
if (index < 230 || index > 310) {
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
let y = radius * Math.sin((Math.PI * 2 * index) / 360);
data.push([x + origin[0], y + origin[1]]);
}
}
return data;
}
function getHexPolygonVertices() {
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()) || {
width: 0,
height: 0,
};
if (isNaN(dom.width) || dom.width < 1) {
return;
}
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const nodeArr = networkProfilingStore.nodes.filter(
(d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL"
);
const count = nodeArr.length;
// layout
const centers = hexGrid(p.count, 68, origin); // cube centers
const cubeCenters = [];
if (count > 7) {
for (let i = 0; i < centers.length; i++) {
// const polygon = createPolygon(68, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// centers[i][0] + polygon[v][0],
// centers[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
let c = hexGrid(1, 20, centers[i]);
if (count < 15) {
c = [c[0], c[5]];
} else if (count < 22) {
c = [c[0], c[2], c[5]];
}
cubeCenters.push(...c);
}
shuffleArray(cubeCenters);
}
// for (let i = 0; i < cubeCenters.length; i++) {
// const polygon = createPolygon(20, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// cubeCenters[i][0] + polygon[v][0],
// cubeCenters[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
// }
let cubes = count > 7 ? cubeCenters : centers;
for (let v = 0; v < count; v++) {
const x = cubes[v][0];
const y = cubes[v][1];
nodeArr[v].x = x;
nodeArr[v].y = y;
}
const outNodes = networkProfilingStore.nodes.filter(
(d: ProcessNode) => !(d.isReal || d.name === "UNKNOWN_LOCAL")
);
const interval = 30;
let r = 250;
let pointArr = getCirclePoint(r, interval);
for (let v = 0; v < outNodes.length; v++) {
if (!pointArr[v]) {
r = r + 80;
pointArr = [...pointArr, ...getCirclePoint(r, interval)];
}
outNodes[v].x = pointArr[v][0];
outNodes[v].y = pointArr[v][1];
}
nodeList.value = [...nodeArr, ...outNodes];
const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
moveNode(d);
/*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
d3.selectAll(".node").call(drag);
}
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const height = ref<number>(100);
const width = ref<number>(100);
const chart = ref<Nullable<HTMLDivElement>>(null);
const tooltip = ref<Nullable<any>>(null);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const config = ref<any>(props.config || {});
const diff = ref<number[]>([220, 200]);
const radius = 210;
const dates = ref<Nullable<{ start: number; end: number }>>(null);
const nodeList = ref<ProcessNode[]>([]);
const currentNode = ref<Nullable<ProcessNode>>(null);
const origin = [0, 0];
function shuffleArray(array: number[][]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function handleLinkClick(event: any, d: Call) {
event.stopPropagation();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(d);
if (!config.value.linkDashboard) {
return;
}
const { dashboard } = getDashboard({
name: config.value.linkDashboard,
layer: dashboardStore.layerId,
entity: EntityType[7].value,
});
if (!dashboard) {
ElMessage.error(
`The dashboard named ${config.value.linkDashboard} doesn't exist`
);
return;
}
let times: any = {};
if (dates.value) {
times = dates.value;
} else {
const { taskStartTime, fixedTriggerDuration } =
networkProfilingStore.selectedNetworkTask;
const startTime =
fixedTriggerDuration > 1800
? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000
: taskStartTime;
times = {
start: startTime,
end: taskStartTime + fixedTriggerDuration * 1000,
onMounted(() => {
init();
oldVal.value = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
}
const param = JSON.stringify({
...times,
step: appStore.duration.step,
utc: appStore.utc,
});
const path = `/dashboard/${dashboard.layer}/${EntityType[7].value}/${d.source.serviceId}/${d.source.serviceInstanceId}/${d.source.id}/${d.target.serviceId}/${d.target.serviceInstanceId}/${d.target.id}/${dashboard.name}/duration/${param}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
function updateSettings(param: unknown) {
config.value = param;
}
function setConfig() {
dashboardStore.selectWidget(props.config);
}
function getDates(times: any) {
dates.value = times;
}
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
) {
async function init() {
if (!networkProfilingStore.nodes.length) {
return;
}
svg.value = d3.select(".process-svg");
graph.value = d3.select(".svg-graph");
tooltip.value = d3.select("#tooltip");
freshNodes();
oldVal.value = { width: cr.width, height: cr.height };
});
if (chart.value) {
observer.observe(chart.value);
useThrottleFn(resize, 500)();
}
}
async function freshNodes() {
if (!networkProfilingStore.nodes.length) {
return;
function drawGraph() {
const dom = chart.value?.getBoundingClientRect() || {
height: 20,
width: 0,
};
height.value = (dom.height || 40) - 20;
width.value = dom.width;
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
svg.value.call(zoom(d3, graph.value, diff.value));
}
drawGraph();
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;
function clickTopology(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
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) {
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
let x, y, yn, p;
const gLayout = new Layout(radius, origin);
const pos = [];
for (x = -n; x <= n; x++) {
y = Math.max(-n, -x - n); // 0
yn = Math.min(n, -x + n); // 1
for (y; y <= yn; y++) {
p = gLayout.axialToPixel(x, y);
pos.push(p);
}
}
return pos;
}
function createPolygon(radius: number, sides = 6, offset = 0) {
const poly: number[][] = [];
let i, rad;
for (i = 0; i < sides; i++) {
rad = Math.PI * 2 * (i / sides);
poly.push([Math.cos(rad + offset) * radius, Math.sin(rad + offset) * radius]);
}
return poly;
}
function getCirclePoint(radius: number, p = 1) {
const data = [];
for (let index = 0; index < 360; index = index + p) {
if (index < 230 || index > 310) {
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
let y = radius * Math.sin((Math.PI * 2 * index) / 360);
data.push([x + origin[0], y + origin[1]]);
}
}
return data;
}
function getHexPolygonVertices() {
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()) || {
width: 0,
height: 0,
};
if (isNaN(dom.width) || dom.width < 1) {
return;
}
} else {
if (inside) {
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const nodeArr = networkProfilingStore.nodes.filter((d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL");
const count = nodeArr.length;
// layout
const centers = hexGrid(p.count, 68, origin); // cube centers
const cubeCenters = [];
if (count > 7) {
for (let i = 0; i < centers.length; i++) {
// const polygon = createPolygon(68, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// centers[i][0] + polygon[v][0],
// centers[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
let c = hexGrid(1, 20, centers[i]);
if (count < 15) {
c = [c[0], c[5]];
} else if (count < 22) {
c = [c[0], c[2], c[5]];
}
cubeCenters.push(...c);
}
shuffleArray(cubeCenters);
}
// for (let i = 0; i < cubeCenters.length; i++) {
// const polygon = createPolygon(20, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// cubeCenters[i][0] + polygon[v][0],
// cubeCenters[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
// }
let cubes = count > 7 ? cubeCenters : centers;
for (let v = 0; v < count; v++) {
const x = cubes[v][0];
const y = cubes[v][1];
nodeArr[v].x = x;
nodeArr[v].y = y;
}
const outNodes = networkProfilingStore.nodes.filter((d: ProcessNode) => !(d.isReal || d.name === "UNKNOWN_LOCAL"));
const interval = 30;
let r = 250;
let pointArr = getCirclePoint(r, interval);
for (let v = 0; v < outNodes.length; v++) {
if (!pointArr[v]) {
r = r + 80;
pointArr = [...pointArr, ...getCirclePoint(r, interval)];
}
outNodes[v].x = pointArr[v][0];
outNodes[v].y = pointArr[v][1];
}
nodeList.value = [...nodeArr, ...outNodes];
const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
moveNode(d);
});
d3.selectAll(".node").call(drag);
}
function shuffleArray(array: number[][]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function handleLinkClick(event: any, d: Call) {
event.stopPropagation();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(d);
if (!config.value.linkDashboard) {
return;
}
}
nodeList.value = nodeList.value.map((node: ProcessNode) => {
if (currentNode.value && node.id === currentNode.value.id) {
node.x = d.x;
node.y = d.y;
const { dashboard } = getDashboard({
name: config.value.linkDashboard,
layer: dashboardStore.layerId,
entity: EntityType[7].value,
});
if (!dashboard) {
ElMessage.error(`The dashboard named ${config.value.linkDashboard} doesn't exist`);
return;
}
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);
let times: any = {};
if (dates.value) {
times = dates.value;
} else {
const { taskStartTime, fixedTriggerDuration } = networkProfilingStore.selectedNetworkTask;
const startTime =
fixedTriggerDuration > 1800 ? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000 : taskStartTime;
times = {
start: startTime,
end: taskStartTime + fixedTriggerDuration * 1000,
};
}
const param = JSON.stringify({
...times,
step: appStore.duration.step,
utc: appStore.utc,
});
const path = `/dashboard/${dashboard.layer}/${EntityType[7].value}/${d.source.serviceId}/${d.source.serviceInstanceId}/${d.source.id}/${d.target.serviceId}/${d.target.serviceInstanceId}/${d.target.id}/${dashboard.name}/duration/${param}`;
const routeUrl = router.resolve({ path });
if (dx < halfSideLen) {
return dy <= halfSideLen * mathSqrt3;
} else {
const maxY = -mathSqrt3 * (dx - halfSideLen) + halfSideLen * mathSqrt3;
return dy < maxY;
window.open(routeUrl.href, "_blank");
}
}
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";
function updateSettings(param: unknown) {
config.value = param;
}
if (types.includes("http")) {
l = "HTTP";
function setConfig() {
dashboardStore.selectWidget(props.config);
}
if (types.includes("tls")) {
l = "TLS";
function getDates(times: any) {
dates.value = times;
}
const tipHtml = `<div><span class="grey">${t(
"detectPoint"
)}: </span>${link.detectPoints.join(" | ")}</div>
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() {
if (!networkProfilingStore.nodes.length) {
return;
}
drawGraph();
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(
() => networkProfilingStore.nodes,
() => {
freshNodes();
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(
() => networkProfilingStore.nodes,
() => {
freshNodes();
},
);
</script>
<style lang="scss">
.process-topo {
width: 100%;
height: 100%;
min-height: 150px;
min-width: 300px;
overflow: auto;
}
.process-svg {
width: 100%;
height: calc(100% - 10px);
cursor: move;
}
.topo-line-anchor {
cursor: pointer;
}
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 10px;
}
.range {
right: 50px;
}
.topo-call {
stroke-linecap: round;
stroke-width: 2px;
stroke-dasharray: 13 7;
fill: none;
animation: topo-dash 0.5s linear infinite;
}
@keyframes topo-dash {
from {
stroke-dashoffset: 20;
.process-topo {
width: 100%;
height: 100%;
min-height: 150px;
min-width: 300px;
overflow: auto;
}
to {
stroke-dashoffset: 0;
.process-svg {
width: 100%;
height: calc(100% - 10px);
cursor: move;
}
}
.time-ranges {
width: 100%;
padding: 10px;
}
.topo-line-anchor {
cursor: pointer;
}
.query {
margin-left: 510px;
}
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 10px;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: 1px solid #000;
border-radius: 3px;
background-color: #fff;
}
.range {
right: 50px;
}
.topo-call {
stroke-linecap: round;
stroke-width: 2px;
stroke-dasharray: 13 7;
fill: none;
animation: topo-dash 0.5s linear infinite;
}
@keyframes topo-dash {
from {
stroke-dashoffset: 20;
}
to {
stroke-dashoffset: 0;
}
}
.time-ranges {
width: 100%;
padding: 10px;
}
.query {
margin-left: 510px;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: 1px solid #000;
border-radius: 3px;
background-color: #fff;
}
</style>

View File

@@ -25,62 +25,54 @@ limitations under the License. -->
/>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import { DashboardItem } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType } from "@/views/dashboard/data";
/*global defineEmits */
const emits = defineEmits(["update"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const linkDashboards = ref<
(DashboardItem & { label: string; value: string })[]
>([]);
import { ref, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import type { DashboardItem } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType } from "@/views/dashboard/data";
/*global defineEmits */
const emits = defineEmits(["update"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const linkDashboards = ref<(DashboardItem & { label: string; value: string })[]>([]);
onMounted(() => {
getDashboards();
});
onMounted(() => {
getDashboards();
});
function getDashboards() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
linkDashboards.value = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
if (
d.layer === dashboardStore.layerId &&
d.entity === EntityType[7].value
) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[]
);
}
function getDashboards() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
linkDashboards.value = list.reduce(
(prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId && d.entity === EntityType[7].value) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[],
);
}
function changeLinkDashboard(opt: { value: string }[]) {
const linkDashboard = opt.length ? opt[0].value : "";
const p = {
...dashboardStore.selectedGrid,
linkDashboard,
};
dashboardStore.selectWidget(p);
dashboardStore.setConfigs(p);
emits("update", p);
}
function changeLinkDashboard(opt: { value: string }[]) {
const linkDashboard = opt.length ? opt[0].value : "";
const p = {
...dashboardStore.selectedGrid,
linkDashboard,
};
dashboardStore.selectWidget(p);
dashboardStore.setConfigs(p);
emits("update", p);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 12px;
margin-top: 10px;
}
.label {
font-size: 12px;
margin-top: 10px;
}
.inputs {
margin-top: 8px;
width: 270px;
margin-bottom: 30px;
}
.inputs {
margin-top: 8px;
width: 270px;
margin-bottom: 30px;
}
</style>

View File

@@ -18,18 +18,11 @@ limitations under the License. -->
<div class="profile-t-tool">
<span>{{ t("taskList") }}</span>
<span class="new-task cp" @click="createTask">
<Icon
:style="{ color: inProcess ? '#ccc' : '#000' }"
iconName="library_add"
size="middle"
/>
<Icon :style="{ color: inProcess ? '#ccc' : '#000' }" iconName="library_add" size="middle" />
</span>
</div>
<div class="profile-t-wrapper">
<div
class="no-data"
v-show="!networkProfilingStore.networkTasks.length"
>
<div class="no-data" v-show="!networkProfilingStore.networkTasks.length">
{{ t("noData") }}
</div>
<table class="profile-t">
@@ -42,8 +35,7 @@ limitations under the License. -->
<td
class="profile-td"
:class="{
selected:
networkProfilingStore.selectedNetworkTask.taskId === i.taskId,
selected: networkProfilingStore.selectedNetworkTask.taskId === i.taskId,
}"
>
<div class="ell">
@@ -51,9 +43,7 @@ limitations under the License. -->
{{ dateFormat(i.taskStartTime) }}
</span>
<span class="mr-10 sm">
{{
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
}}
{{ dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000) }}
</span>
<span class="new-task" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
@@ -68,247 +58,215 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<el-dialog v-model="viewDetail" :destroy-on-close="true" fullscreen @closed="viewDetail = false">
<TaskDetails :details="networkProfilingStore.selectedNetworkTask" />
</el-dialog>
<el-dialog
v-model="newTask"
:title="t('taskTitle')"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<el-dialog v-model="newTask" :title="t('taskTitle')" :destroy-on-close="true" fullscreen @closed="newTask = false">
<NewTask @create="saveNewTask" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
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, { dateFormat } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import type { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
import TaskDetails from "../../components/TaskDetails.vue";
import dateFormatStep, { dateFormat } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue";
/*global Nullable */
const { t } = useI18n();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const appStore = useAppStoreWithOut();
const viewDetail = ref<boolean>(false);
const newTask = ref<boolean>(false);
const intervalFn = ref<Nullable<any>>(null);
const intervalKeepAlive = ref<Nullable<any>>(null);
const inProcess = ref<boolean>(false);
/*global Nullable */
const { t } = useI18n();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const appStore = useAppStoreWithOut();
const viewDetail = ref<boolean>(false);
const newTask = ref<boolean>(false);
const intervalFn = ref<Nullable<any>>(null);
const intervalKeepAlive = ref<Nullable<any>>(null);
const inProcess = ref<boolean>(false);
fetchTasks();
fetchTasks();
async function changeTask(item: EBPFTaskList) {
networkProfilingStore.setSelectedNetworkTask(item);
intervalFn.value && clearInterval(intervalFn.value);
getTopology();
}
async function getTopology() {
const { taskStartTime, fixedTriggerDuration, taskId } =
networkProfilingStore.selectedNetworkTask;
const serviceInstanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
const startTime =
fixedTriggerDuration > 1800
? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000
: taskStartTime;
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime()) {
endTime = new Date().getTime();
async function changeTask(item: EBPFTaskList) {
networkProfilingStore.setSelectedNetworkTask(item);
intervalFn.value && clearInterval(intervalFn.value);
getTopology();
}
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(
getLocalTime(appStore.utc, new Date(startTime)),
appStore.duration.step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(endTime)),
appStore.duration.step,
true
),
step: appStore.duration.step,
async function getTopology() {
const { taskStartTime, fixedTriggerDuration, taskId } = networkProfilingStore.selectedNetworkTask;
const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
const startTime =
fixedTriggerDuration > 1800 ? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000 : taskStartTime;
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime()) {
endTime = new Date().getTime();
}
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(getLocalTime(appStore.utc, new Date(startTime)), appStore.duration.step, true),
end: dateFormatStep(getLocalTime(appStore.utc, new Date(endTime)), appStore.duration.step, true),
step: appStore.duration.step,
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
}
const task = networkProfilingStore.networkTasks[0] || {};
if (task.taskId === taskId) {
inProcess.value = task.taskStartTime + task.fixedTriggerDuration * 1000 > new Date().getTime() ? true : false;
}
if (!inProcess.value) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
return resp;
}
function createTask() {
if (inProcess.value) {
return;
}
newTask.value = true;
}
async function saveNewTask(
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[],
) {
const instanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
if (!instanceId) {
return ElMessage.error("No Instance ID");
}
const res = await networkProfilingStore.createNetworkTask(instanceId, params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createEBPFNetworkProfiling.status) {
ElMessage.error(res.data.createEBPFNetworkProfiling.errorReason);
return;
}
newTask.value = false;
await fetchTasks();
}
function enableInterval() {
intervalFn.value = setInterval(getTopology, 30000);
}
function networkInterval() {
intervalKeepAlive.value = setInterval(keepAliveNetwork, 60000);
}
async function keepAliveNetwork() {
const res = await networkProfilingStore.keepNetworkProfiling(networkProfilingStore.selectedNetworkTask.taskId);
if (res.errors) {
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
return ElMessage.error(res.errors);
}
if (!networkProfilingStore.aliveNetwork) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
}
async function fetchTasks() {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
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);
}
if (!networkProfilingStore.networkTasks.length) {
return;
}
await getTopology();
if (inProcess.value) {
enableInterval();
networkInterval();
keepAliveNetwork();
}
}
watch(
() => selectorStore.currentPod,
() => {
inProcess.value = false;
fetchTasks();
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
}
const task = networkProfilingStore.networkTasks[0] || {};
if (task.taskId === taskId) {
inProcess.value =
task.taskStartTime + task.fixedTriggerDuration * 1000 >
new Date().getTime()
? true
: false;
}
if (!inProcess.value) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
return resp;
}
function createTask() {
if (inProcess.value) {
return;
}
newTask.value = true;
}
async function saveNewTask(
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[]
) {
const instanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
if (!instanceId) {
return ElMessage.error("No Instance ID");
}
const res = await networkProfilingStore.createNetworkTask(instanceId, params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createEBPFNetworkProfiling.status) {
ElMessage.error(res.data.createEBPFNetworkProfiling.errorReason);
return;
}
newTask.value = false;
await fetchTasks();
}
function enableInterval() {
intervalFn.value = setInterval(getTopology, 30000);
}
function networkInterval() {
intervalKeepAlive.value = setInterval(keepAliveNetwork, 60000);
}
async function keepAliveNetwork() {
const res = await networkProfilingStore.keepNetworkProfiling(
networkProfilingStore.selectedNetworkTask.taskId
);
if (res.errors) {
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
return ElMessage.error(res.errors);
}
if (!networkProfilingStore.aliveNetwork) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
}
async function fetchTasks() {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
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);
}
if (!networkProfilingStore.networkTasks.length) {
return;
}
await getTopology();
if (inProcess.value) {
enableInterval();
networkInterval();
keepAliveNetwork();
}
}
watch(
() => selectorStore.currentPod,
() => {
inProcess.value = false;
fetchTasks();
}
);
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 330px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.item span {
height: 21px;
}
.profile-td {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
.profile-task-list {
width: 330px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
}
.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);
.item span {
height: 21px;
}
}
.profile-t-tool {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
width: 100%;
}
.profile-td {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
.new-task {
float: right;
}
&.selected {
background-color: #ededed;
}
}
.reload {
margin-left: 30px;
}
.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: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
width: 100%;
}
.new-task {
float: right;
}
.reload {
margin-left: 30px;
}
</style>

View File

@@ -13,166 +13,141 @@ 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-popover
placement="bottom"
:width="600"
trigger="click"
@after-enter="showTimeLine"
>
<el-popover placement="bottom" :width="600" trigger="click" @after-enter="showTimeLine">
<template #reference>
<div class="switch-icon-edit">
<Icon size="middle" iconName="time_range" />
</div>
</template>
<div ref="timeRange" class="time-ranges"></div>
<el-button
class="query"
size="small"
type="primary"
@click="updateTopology"
>
<el-button class="query" size="small" type="primary" @click="updateTopology">
{{ t("query") }}
</el-button>
</el-popover>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
/*global Nullable, defineEmits */
const emits = defineEmits(["get"]);
const { t } = useI18n();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const networkProfilingStore = useNetworkProfilingStore();
const timeRange = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const task = ref<any[]>([]);
const isUpdate = ref<boolean>(false);
/*global Nullable, defineEmits */
const emits = defineEmits(["get"]);
const { t } = useI18n();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const networkProfilingStore = useNetworkProfilingStore();
const timeRange = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const task = ref<any[]>([]);
const isUpdate = ref<boolean>(false);
function showTimeLine() {
visTimeline();
}
function visTimeline() {
if (!timeRange.value) {
return;
function showTimeLine() {
visTimeline();
}
if (!networkProfilingStore.selectedNetworkTask.taskId) {
return;
}
const { taskStartTime, fixedTriggerDuration, targetType, taskId } =
networkProfilingStore.selectedNetworkTask;
if (task.value[0] && task.value[0].data.taskId === taskId) {
if (isUpdate.value) {
function visTimeline() {
if (!timeRange.value) {
return;
}
if (!networkProfilingStore.selectedNetworkTask.taskId) {
return;
}
const { taskStartTime, fixedTriggerDuration, targetType, taskId } = networkProfilingStore.selectedNetworkTask;
if (task.value[0] && task.value[0].data.taskId === taskId) {
if (isUpdate.value) {
return;
}
}
if (visGraph.value) {
visGraph.value.destroy();
}
isUpdate.value = false;
let startTime = taskStartTime;
if (fixedTriggerDuration > 1800) {
startTime = taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000;
}
const d = networkProfilingStore.networkTasks[0] || {};
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime() && taskId === d.taskId) {
endTime = new Date().getTime();
}
task.value = [
{
id: 1,
content: "",
start: new Date(startTime),
end: new Date(endTime),
data: networkProfilingStore.selectedNetworkTask,
className: targetType,
},
];
const items: any = new DataSet(task.value);
items.on("update", (event: string, properties: any) => {
task.value = properties.data;
});
const itemsAlwaysDraggable =
fixedTriggerDuration > 1800
? {
item: true,
range: true,
}
: undefined;
const editable =
fixedTriggerDuration > 1800
? {
updateTime: true,
}
: false;
const options = {
height: 150,
width: "100%",
locale: "en",
editable,
zoomMin: 1000 * 60,
zoomMax: 1000 * 60 * 60 * 24,
};
const opt = itemsAlwaysDraggable ? { ...options, itemsAlwaysDraggable } : options;
visGraph.value = new Timeline(timeRange.value, items, opt);
}
if (visGraph.value) {
visGraph.value.destroy();
}
isUpdate.value = false;
let startTime = taskStartTime;
if (fixedTriggerDuration > 1800) {
startTime = taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000;
}
const d = networkProfilingStore.networkTasks[0] || {};
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (
taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime() &&
taskId === d.taskId
) {
endTime = new Date().getTime();
}
task.value = [
{
id: 1,
content: "",
start: new Date(startTime),
end: new Date(endTime),
data: networkProfilingStore.selectedNetworkTask,
className: targetType,
},
];
const items: any = new DataSet(task.value);
items.on("update", (event: string, properties: any) => {
task.value = properties.data;
});
const itemsAlwaysDraggable =
fixedTriggerDuration > 1800
? {
item: true,
range: true,
}
: undefined;
const editable =
fixedTriggerDuration > 1800
? {
updateTime: true,
}
: false;
const options = {
height: 150,
width: "100%",
locale: "en",
editable,
zoomMin: 1000 * 60,
zoomMax: 1000 * 60 * 60 * 24,
};
const opt = itemsAlwaysDraggable
? { ...options, itemsAlwaysDraggable }
: options;
visGraph.value = new Timeline(timeRange.value, items, opt);
}
async function updateTopology() {
isUpdate.value = true;
emits("get", {
start: task.value[0].start.getTime(),
end: task.value[0].end.getTime(),
});
const serviceInstanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
async function updateTopology() {
isUpdate.value = true;
emits("get", {
start: task.value[0].start.getTime(),
end: task.value[0].end.getTime(),
});
const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(
getLocalTime(appStore.utc, new Date(task.value[0].start)),
appStore.duration.step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(task.value[0].end)),
appStore.duration.step,
true
),
step: appStore.duration.step,
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(getLocalTime(appStore.utc, new Date(task.value[0].start)), appStore.duration.step, true),
end: dateFormatStep(getLocalTime(appStore.utc, new Date(task.value[0].end)), appStore.duration.step, true),
step: appStore.duration.step,
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
}
return resp;
}
return resp;
}
</script>
<style lang="scss" scoped>
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 50px;
}
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 50px;
}
</style>