feat: Move topology to widgets (#29)

This commit is contained in:
Fine0830 2022-03-20 19:45:31 +08:00 committed by GitHub
parent 597e98e291
commit 42d8e909f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 279 additions and 348 deletions

View File

@ -49,7 +49,7 @@ const props = defineProps({
default: () => [], default: () => [],
}, },
value: { value: {
type: [Array, String] as PropType<string[] | string>, type: [Array, String, Number] as PropType<any>,
default: () => [], default: () => [],
}, },
size: { type: null, default: "default" }, size: { type: null, default: "default" },

View File

@ -112,6 +112,7 @@ const msg = {
showGroup: "Show Group", showGroup: "Show Group",
noRoot: "Please set a root dashboard for", noRoot: "Please set a root dashboard for",
noWidget: "Please add widgets.", noWidget: "Please add widgets.",
rename: "Rename",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
secondTip: "Select Second", secondTip: "Select Second",

View File

@ -112,6 +112,7 @@ const msg = {
noRoot: "请设置根仪表板,为", noRoot: "请设置根仪表板,为",
showGroup: "显示分组", showGroup: "显示分组",
noWidget: "请添加组件", noWidget: "请添加组件",
rename: "重命名",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
secondTip: "选择秒数", secondTip: "选择秒数",

View File

@ -27,6 +27,7 @@ import { Duration } from "@/types/app";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { EntityType } from "@/views/dashboard/data";
interface DashboardState { interface DashboardState {
showConfig: boolean; showConfig: boolean;
layout: LayoutConfig[]; layout: LayoutConfig[];
@ -92,15 +93,15 @@ export const dashboardStore = defineStore({
]; ];
} }
if (type === "Topology") { if (type === "Topology") {
newItem.w = 4; newItem.h = 36;
newItem.h = 6;
newItem.graph = { newItem.graph = {
fontColor: "white",
backgroundColor: "green",
iconTheme: true,
content: "Topology",
fontSize: 18,
showDepth: true, showDepth: true,
depth:
this.entity === EntityType[1].value
? 1
: this.entity === EntityType[0].value
? 2
: 3,
}; };
} }
if (type === "Trace" || type === "Profile" || type === "Log") { if (type === "Trace" || type === "Profile" || type === "Log") {
@ -144,19 +145,13 @@ export const dashboardStore = defineStore({
metrics: [""], metrics: [""],
}; };
if (type === "Topology") { if (type === "Topology") {
newItem.w = 4; newItem.h = 32;
newItem.h = 6;
newItem.graph = { newItem.graph = {
fontColor: "white",
backgroundColor: "green",
iconTheme: true,
content: "Topology",
fontSize: 18,
showDepth: true, showDepth: true,
}; };
} }
if (type === "Trace" || type === "Profile" || type === "Log") { if (type === "Trace" || type === "Profile" || type === "Log") {
newItem.h = 24; newItem.h = 32;
} }
if (this.layout[idx].children) { if (this.layout[idx].children) {
const items = children.map((d: LayoutConfig) => { const items = children.map((d: LayoutConfig) => {

View File

@ -129,6 +129,6 @@ export interface TopologyConfig {
iconTheme?: boolean; iconTheme?: boolean;
content?: string; content?: string;
fontSize?: number; fontSize?: number;
depth?: string; depth?: number;
showDepth?: boolean; showDepth?: boolean;
} }

View File

@ -28,16 +28,7 @@ limitations under the License. -->
@closed="dashboardStore.setConfigPanel(false)" @closed="dashboardStore.setConfigPanel(false)"
> >
<TopologyConfig v-if="dashboardStore.selectedGrid.type === 'Topology'" /> <TopologyConfig v-if="dashboardStore.selectedGrid.type === 'Topology'" />
<Widget v-else /> <WidgetConfig v-else />
</el-dialog>
<el-dialog
v-model="dashboardStore.showTopology"
:destroy-on-close="true"
fullscreen
@closed="dashboardStore.setTopology(false)"
custom-class="dark-dialog"
>
<Topology />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -47,9 +38,8 @@ import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import GridLayout from "./panel/Layout.vue"; import GridLayout from "./panel/Layout.vue";
import Tool from "./panel/Tool.vue"; import Tool from "./panel/Tool.vue";
import Widget from "./configuration/Widget.vue";
import TopologyConfig from "./configuration/Topology.vue"; import TopologyConfig from "./configuration/Topology.vue";
import Topology from "./related/topology/Index.vue"; import WidgetConfig from "./configuration/Widget.vue";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";

View File

@ -60,7 +60,7 @@ limitations under the License. -->
{{ t("view") }} {{ t("view") }}
</el-button> </el-button>
<el-button size="small" @click="handleEdit(scope.row)"> <el-button size="small" @click="handleEdit(scope.row)">
{{ t("edit") }} {{ t("rename") }}
</el-button> </el-button>
<el-popconfirm <el-popconfirm
title="Are you sure to delete this?" title="Are you sure to delete this?"
@ -122,7 +122,6 @@ import router from "@/router";
import { DashboardItem } from "@/types/dashboard"; import { DashboardItem } from "@/types/dashboard";
import { saveFile, readFile } from "@/utils/file"; import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data"; import { EntityType } from "./data";
import { findLastKey } from "lodash";
/*global Nullable*/ /*global Nullable*/
const { t } = useI18n(); const { t } = useI18n();
@ -134,17 +133,7 @@ const loading = ref<boolean>(false);
const multipleTableRef = ref<InstanceType<typeof ElTable>>(); const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]); const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null); const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
// # - os-linux
// # - k8s
// # - general(agent-installed)
// # - faas
// # - mesh
// # - mesh-cp
// # - mesh-dp
// # - database
// # - cache
// # - browser
// # - skywalking
appStore.setPageTitle("Dashboard List"); appStore.setPageTitle("Dashboard List");
const handleSelectionChange = (val: DashboardItem[]) => { const handleSelectionChange = (val: DashboardItem[]) => {
multipleSelection.value = val; multipleSelection.value = val;

View File

@ -4,17 +4,32 @@ this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0 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 not use this file except in compliance with
the License. You may obtain a copy of the License at the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 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>
<WidgetOptions /> <div class="item">
<StyleOptions /> <span class="label">{{ t("showDepth") }}</span>
<el-switch
v-model="showDepth"
active-text="Yes"
inactive-text="No"
@change="changeConfig({ showDepth })"
/>
</div>
<div class="item" v-show="showDepth">
<span class="label">{{ t("defaultDepth") }}</span>
<Selector
class="input"
size="small"
:value="depth"
:options="DepthList"
@change="changeDepth($event)"
/>
</div>
<div class="footer"> <div class="footer">
<el-button size="small"> <el-button size="small">
{{ t("cancel") }} {{ t("cancel") }}
@ -25,18 +40,34 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import WidgetOptions from "./components/WidgetOptions.vue";
import StyleOptions from "./topology/StyleOptions.vue";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { DepthList } from "../data";
import { Option } from "@/types/app";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore;
const showDepth = ref<boolean>(selectedGrid.graph.showDepth);
const depth = ref<number>(selectedGrid.graph.depth || 2);
function applyConfig() { function applyConfig() {
dashboardStore.setConfigs(dashboardStore.selectedGrid); dashboardStore.setConfigs(dashboardStore.selectedGrid);
dashboardStore.setConfigPanel(false); dashboardStore.setConfigPanel(false);
} }
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function changeDepth(opt: Option[] | any) {
const val = opt[0].value;
changeConfig({ depth: val });
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.footer { .footer {
@ -49,4 +80,15 @@ function applyConfig() {
width: 100%; width: 100%;
background-color: #fff; background-color: #fff;
} }
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin: 10px 0;
}
</style> </style>

View File

@ -82,7 +82,7 @@ import { useAppStoreWithOut } from "@/store/modules/app";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import graphs from "../graphs"; import graphs from "../graphs";
import configs from "./widget/graph-styles"; import configs from "./widget/graph-styles";
import WidgetOptions from "./components/WidgetOptions.vue"; import WidgetOptions from "./widget/WidgetOptions.vue";
import StandardOptions from "./widget/StandardOptions.vue"; import StandardOptions from "./widget/StandardOptions.vue";
import MetricOptions from "./widget/MetricOptions.vue"; import MetricOptions from "./widget/MetricOptions.vue";

View File

@ -1,151 +0,0 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("backgroundColors") }}</span>
<Selector
:value="backgroundColor"
:options="colors"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ backgroundColor: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontSize") }}</span>
<el-slider
class="slider"
v-model="fontSize"
show-input
input-size="small"
:min="12"
:max="30"
:step="1"
@change="changeConfig({ fontSize })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontColors") }}</span>
<Selector
:value="fontColor"
:options="colors"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ fontColor: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("iconTheme") }}</span>
<el-switch
v-model="iconTheme"
active-text="Light"
inactive-text="Dark"
@change="changeConfig({ iconTheme })"
/>
</div>
<div class="item">
<span class="label">{{ t("content") }}</span>
<el-input
class="input"
v-model="content"
size="small"
@change="changeConfig({ content })"
/>
</div>
<div class="item">
<span class="label">{{ t("showDepth") }}</span>
<el-switch
v-model="showDepth"
active-text="Yes"
inactive-text="No"
@change="changeConfig({ showDepth })"
/>
</div>
<div class="item" v-show="showDepth">
<span class="label">{{ t("defaultDepth") }}</span>
<Selector
class="input"
size="small"
:value="depth"
:options="DepthList"
@change="changeDepth($event)"
/>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { DepthList } from "../../data";
import { Option } from "@/types/app";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore;
const iconTheme = ref(selectedGrid.graph.iconTheme || true);
const backgroundColor = ref(selectedGrid.graph.backgroundColor || "green");
const fontColor = ref(selectedGrid.graph.fontColor || "white");
const content = ref<string>(selectedGrid.graph.content);
const fontSize = ref<number>(selectedGrid.graph.fontSize);
const depth = ref<string>(selectedGrid.graph.depth || "2");
const showDepth = ref<boolean>(selectedGrid.graph.showDepth);
const colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function changeDepth(opt: Option[]) {
const val = opt[0].value;
changeConfig({ depth: val });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
</style>

View File

@ -97,6 +97,7 @@ limitations under the License. -->
:key="item.i" :key="item.i"
@click="clickTabGrid($event, item)" @click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }" :class="{ active: activeTabWidget === item.i }"
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart"
> >
<component <component
:is="item.type" :is="item.type"

View File

@ -13,58 +13,28 @@ 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 class="topology"> <div class="topology flex-v">
<div class="header flex-h"> <div class="operation">
<div>{{ data.widget?.title || "" }}</div> <el-popover
<div> placement="bottom"
<el-tooltip :content="data.widget?.tips"> trigger="click"
<span> :width="100"
<Icon v-if="routeParams.entity"
iconName="info_outline"
size="sm"
class="operation"
v-show="data.widget?.tips"
/>
</span>
</el-tooltip>
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="routeParams.entity"
>
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="editConfig">
<span>{{ t("edit") }}</span>
</div>
<div class="tools" @click="removeTopo">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
</div>
</div>
<div
class="body"
@click="ViewTopology"
:style="{ backgroundColor: Colors[data.graph.backgroundColor] }"
>
<Icon
:iconName="data.graph.iconTheme ? 'topology-light' : 'topology-dark'"
size="middle"
/>
<div
:style="{
color: Colors[data.graph.fontColor],
fontSize: data.graph.fontSize + 'px',
}"
> >
{{ data.graph.content }} <template #reference>
</div> <span>
<Icon iconName="ellipsis_v" size="middle" />
</span>
</template>
<div class="tools" @click="editConfig">
<span>{{ t("edit") }}</span>
</div>
<div class="tools" @click="removeTopo">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
</div> </div>
<Topology :config="props.data" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -72,7 +42,8 @@ import type { PropType } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { Colors } from "../data"; import Topology from "../related/topology/Index.vue";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
data: { data: {
@ -85,32 +56,27 @@ const { t } = useI18n();
const routeParams = useRoute().params; const routeParams = useRoute().params;
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() { function editConfig() {
dashboardStore.setConfigPanel(true); dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data); dashboardStore.selectWidget(props.data);
} }
function ViewTopology() {
dashboardStore.setTopology(true);
}
function removeTopo() {
dashboardStore.removeControls(props.data);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.topology { .topology {
font-size: 12px; background-color: #333840;
height: 100%;
}
.header {
height: 30px;
padding: 5px;
width: 100%; width: 100%;
border-bottom: 1px solid #eee; height: 100%;
justify-content: space-between; font-size: 12px;
position: relative;
} }
.operation { .operation {
position: absolute;
top: 5px;
right: 3px;
cursor: pointer; cursor: pointer;
} }
@ -127,19 +93,6 @@ function removeTopo() {
} }
} }
.body {
text-align: center;
width: 100%;
height: calc(100% - 30px);
cursor: pointer;
box-sizing: border-box;
color: #333;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
}
.no-data { .no-data {
font-size: 14px; font-size: 14px;
color: #888; color: #888;

View File

@ -108,11 +108,7 @@ export default defineComponent({
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
if ( if (props.needQuery || !dashboardStore.currentDashboard.id) {
dashboardStore.entity === EntityType[1].value ||
props.needQuery ||
!dashboardStore.currentDashboard.id
) {
queryMetrics(); queryMetrics();
} }
@ -165,11 +161,7 @@ export default defineComponent({
} }
); );
watch( watch(
() => [ () => [selectorStore.currentService, selectorStore.currentDestService],
selectorStore.currentService,
selectorStore.currentDestService,
appStore.durationTime,
],
() => { () => {
if ( if (
dashboardStore.entity === EntityType[0].value || dashboardStore.entity === EntityType[0].value ||
@ -188,6 +180,14 @@ export default defineComponent({
queryMetrics(); queryMetrics();
} }
); );
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
queryMetrics();
}
}
);
return { return {
state, state,

View File

@ -186,7 +186,6 @@ export const ServiceTools = [
export const InstanceTools = [ export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" }, { name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
{ name: "save", content: "Apply", id: "apply" }, { name: "save", content: "Apply", id: "apply" },
@ -229,19 +228,10 @@ export enum LegendOpt {
VALUE = "value", VALUE = "value",
CONDITION = "condition", CONDITION = "condition",
} }
export const DepthList = ["1", "2", "3", "4", "5"].map((item: string) => ({ export const DepthList = [1, 2, 3, 4, 5].map((item: number) => ({
value: item, value: item,
label: item, label: item,
})); }));
export const Colors: any = {
green: "#67C23A",
blue: "#409EFF",
red: "#F56C6C",
grey: "#909399",
white: "#fff",
black: "#000",
orange: "#E6A23C",
};
export const Status = [ export const Status = [
{ label: "All", value: "ALL" }, { label: "All", value: "ALL" },
{ label: "Success", value: "SUCCESS" }, { label: "Success", value: "SUCCESS" },

View File

@ -35,7 +35,11 @@ limitations under the License. -->
<template #default="scope"> <template #default="scope">
<router-link <router-link
class="link" class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${selectorStore.currentService.id}/${scope.row.id}/${config.dashboardName}`" :to="`/dashboard/${dashboardStore.layerId}/${
EntityType[2].value
}/${selectorStore.currentService.id}/${
scope.row.id
}/${config.dashboardName.split(' ').join('-')}`"
:style="{ fontSize: `${config.fontSize}px` }" :style="{ fontSize: `${config.fontSize}px` }"
> >
{{ scope.row.label }} {{ scope.row.label }}

View File

@ -35,7 +35,11 @@ limitations under the License. -->
<template #default="scope"> <template #default="scope">
<router-link <router-link
class="link" class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${selectorStore.currentService.id}/${scope.row.id}/${config.dashboardName}`" :to="`/dashboard/${dashboardStore.layerId}/${
EntityType[3].value
}/${selectorStore.currentService.id}/${
scope.row.id
}/${config.dashboardName.split(' ').join('-')}`"
:style="{ fontSize: `${config.fontSize}px` }" :style="{ fontSize: `${config.fontSize}px` }"
> >
{{ scope.row.label }} {{ scope.row.label }}

View File

@ -31,7 +31,7 @@ limitations under the License. -->
:key="item.i" :key="item.i"
@click="clickGrid(item)" @click="clickGrid(item)"
:class="{ active: dashboardStore.activedGridItem === item.i }" :class="{ active: dashboardStore.activedGridItem === item.i }"
drag-ignore-from="svg.d3-trace-tree, .dragger" drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart"
> >
<component :is="item.type" :data="item" /> <component :is="item.type" :data="item" />
</grid-item> </grid-item>

View File

@ -299,6 +299,14 @@ watch(
init(); init();
} }
); );
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.inputs { .inputs {

View File

@ -56,9 +56,14 @@ import { useProfileStore } from "@/store/modules/profile";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue"; import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
const profileStore = useProfileStore(); const profileStore = useProfileStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n(); const { t } = useI18n();
// const service = ref<any>({}); // const service = ref<any>({});
const endpointName = ref<string>(""); const endpointName = ref<string>("");
@ -104,6 +109,14 @@ watch(
searchTasks(); searchTasks();
} }
); );
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.header { .header {

View File

@ -13,18 +13,26 @@ 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>
<PodTopology v-if="isSankey" /> <PodTopology :config="config" v-if="isSankey" />
<Graph v-else /> <Graph :config="config" v-else />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from "vue";
import { ref } from "vue"; import { ref } from "vue";
import Graph from "./components/Graph.vue"; import Graph from "./components/Graph.vue";
import PodTopology from "./components/PodTopology.vue"; import PodTopology from "./components/PodTopology.vue";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const isSankey = ref<boolean>( const isSankey = ref<boolean>(
[EntityType[2].value, EntityType[4].value].includes(dashboardStore.entity) [EntityType[2].value, EntityType[3].value].includes(dashboardStore.entity)
); );
</script> </script>

View File

@ -17,13 +17,14 @@ limitations under the License. -->
ref="chart" ref="chart"
class="micro-topo-chart" class="micro-topo-chart"
v-loading="loading" v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0)"
:style="`height: ${height}px`" :style="`height: ${height}px`"
> >
<div class="setting" v-show="showSetting"> <div class="setting" v-show="showSetting">
<Settings @update="updateSettings" @updateNodes="freshNodes" /> <Settings @update="updateSettings" @updateNodes="freshNodes" />
</div> </div>
<div class="tool"> <div class="tool">
<span v-show="dashboardStore.selectedGrid.showDepth"> <span v-show="config.graph.showDepth">
<span class="label">{{ t("currentDepth") }}</span> <span class="label">{{ t("currentDepth") }}</span>
<Selector <Selector
class="inputs" class="inputs"
@ -63,7 +64,8 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, reactive } from "vue"; import type { PropType } from "vue";
import { ref, onMounted, onBeforeUnmount, reactive, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import * as d3 from "d3"; import * as d3 from "d3";
import d3tip from "d3-tip"; import d3tip from "d3-tip";
@ -82,14 +84,22 @@ import { ElMessage } from "element-plus";
import Settings from "./Settings.vue"; import Settings from "./Settings.vue";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { Service } from "@/types/selector"; import { Service } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
/*global Nullable */ /*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n(); const { t } = useI18n();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore(); const topologyStore = useTopologyStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const height = ref<number>(document.body.clientHeight - 90); const appStore = useAppStoreWithOut();
const width = ref<number>(document.body.clientWidth - 40); const height = ref<number>(100);
const width = ref<number>(100);
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const simulation = ref<any>(null); const simulation = ref<any>(null);
const svg = ref<Nullable<any>>(null); const svg = ref<Nullable<any>>(null);
@ -110,7 +120,7 @@ const items = ref<
{ id: "inspect", title: "Inspect", func: handleInspect }, { id: "inspect", title: "Inspect", func: handleInspect },
{ id: "alarm", title: "Alarm", func: handleGoAlarm }, { id: "alarm", title: "Alarm", func: handleGoAlarm },
]); ]);
const depth = ref<string>(dashboardStore.selectedGrid.depth || "2"); const depth = ref<number>(props.config.graph.depth || 2);
onMounted(async () => { onMounted(async () => {
loading.value = true; loading.value = true;
@ -119,19 +129,23 @@ onMounted(async () => {
if (resp && resp.errors) { if (resp && resp.errors) {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
} }
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
svg.value = d3 svg.value = d3.select(chart.value).append("svg").attr("class", "topo-svg");
.select(chart.value)
.append("svg")
.attr("class", "topo-svg")
.attr("height", height.value)
.attr("width", width.value);
await init(); await init();
update(); update();
}); });
async function init() { async function init() {
tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]); tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]);
graph.value = svg.value.append("g").attr("class", "topo-svg-graph"); graph.value = svg.value
.append("g")
.attr("class", "topo-svg-graph")
.attr("transform", `translate(0, -100)`);
graph.value.call(tip.value); graph.value.call(tip.value);
simulation.value = simulationInit( simulation.value = simulationInit(
d3, d3,
@ -410,8 +424,8 @@ function setConfig() {
showSetting.value = !showSetting.value; showSetting.value = !showSetting.value;
} }
function resize() { function resize() {
height.value = document.body.clientHeight - 90; height.value = document.body.clientHeight;
width.value = document.body.clientWidth - 40; width.value = document.body.clientWidth;
svg.value.attr("height", height.value).attr("width", width.value); svg.value.attr("height", height.value).attr("width", width.value);
} }
function updateSettings(config: any) { function updateSettings(config: any) {
@ -453,7 +467,7 @@ async function freshNodes() {
update(); update();
} }
async function changeDepth(opt: Option[]) { async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value; depth.value = opt[0].value;
await getTopology(); await getTopology();
freshNodes(); freshNodes();
@ -461,17 +475,40 @@ async function changeDepth(opt: Option[]) {
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener("resize", resize); window.removeEventListener("resize", resize);
}); });
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
() => {
freshNodes();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
}
);
</script> </script>
<style lang="scss"> <style lang="scss">
.topo-svg {
width: 100%;
height: calc(100% - 5px);
cursor: move;
}
.micro-topo-chart { .micro-topo-chart {
position: relative; position: relative;
height: calc(100% - 30px);
overflow: auto;
margin-top: 30px;
.setting { .setting {
position: absolute; position: absolute;
top: 70px; top: 80px;
right: 0; right: 10px;
width: 400px; width: 400px;
height: 700px; height: 600px;
background-color: #2b3037; background-color: #2b3037;
overflow: auto; overflow: auto;
padding: 0 15px; padding: 0 15px;
@ -510,8 +547,8 @@ onBeforeUnmount(() => {
.tool { .tool {
position: absolute; position: absolute;
top: 22px; top: 35px;
right: 0; right: 10px;
} }
.switch-icon { .switch-icon {
@ -524,11 +561,6 @@ onBeforeUnmount(() => {
border-radius: 3px; border-radius: 3px;
} }
.topo-svg {
display: block;
width: 100%;
}
.topo-line { .topo-line {
stroke-linecap: round; stroke-linecap: round;
stroke-width: 3px; stroke-width: 3px;

View File

@ -16,8 +16,7 @@ limitations under the License. -->
<div class="tool"> <div class="tool">
<span <span
v-show=" v-show="
dashboardStore.entity === EntityType[2].value && dashboardStore.entity === EntityType[2].value && config.graph.showDepth
dashboardStore.selectedGrid.showDepth
" "
> >
<span class="label">{{ t("currentDepth") }}</span> <span class="label">{{ t("currentDepth") }}</span>
@ -47,6 +46,7 @@ limitations under the License. -->
class="sankey" class="sankey"
:style="`height:${height}px;width:${width}px;`" :style="`height:${height}px;width:${width}px;`"
v-loading="loading" v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0)"
@click="handleClick" @click="handleClick"
> >
<Sankey @click="selectNodeLink" /> <Sankey @click="selectNodeLink" />
@ -72,36 +72,47 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ref, onMounted, reactive } from "vue"; import { ref, onMounted, reactive } from "vue";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useTopologyStore } from "@/store/modules/topology"; import { useTopologyStore } from "@/store/modules/topology";
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 { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType, DepthList } from "../../../data"; import { EntityType, DepthList } from "../../../data";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import Sankey from "./Sankey.vue"; import Sankey from "./Sankey.vue";
import Settings from "./Settings.vue"; import Settings from "./Settings.vue";
import router from "@/router"; import router from "@/router";
/*global defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore(); const topologyStore = useTopologyStore();
const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const height = ref<number>(document.body.clientHeight - 150); const height = ref<number>(100);
const width = ref<number>(document.body.clientWidth - 40); const width = ref<number>(100);
const showSettings = ref<boolean>(false); const showSettings = ref<boolean>(false);
const settings = ref<any>({}); const settings = ref<any>({});
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN }); const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const depth = ref<string>(dashboardStore.selectedGrid.depth || "3"); const depth = ref<number>(props.config.graph.depth || 3);
const items = [ const items = [
{ id: "inspect", title: "Inspect", func: inspect }, { id: "inspect", title: "Inspect", func: inspect },
{ id: "dashboard", title: "View Dashboard", func: goDashboard }, { id: "dashboard", title: "View Dashboard", func: goDashboard },
{ id: "alarm", title: "View Alarm", func: goAlarm }, { id: "alarm", title: "View Alarm", func: goAlarm },
]; ];
onMounted(async () => { onMounted(() => {
loadTopology(selectorStore.currentPod && selectorStore.currentPod.id); loadTopology(selectorStore.currentPod && selectorStore.currentPod.id);
}); });
@ -112,6 +123,12 @@ async function loadTopology(id: string) {
if (resp && resp.errors) { if (resp && resp.errors) {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
} }
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 70,
width: 5,
};
height.value = dom.height - 70;
width.value = dom.width - 5;
} }
function inspect() { function inspect() {
@ -176,7 +193,7 @@ function selectNodeLink(d: any) {
operationsPos.y = d.event.event.clientY; operationsPos.y = d.event.event.clientY;
} }
async function changeDepth(opt: Option[]) { async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value; depth.value = opt[0].value;
loadTopology(selectorStore.currentPod.id); loadTopology(selectorStore.currentPod.id);
} }
@ -190,7 +207,7 @@ async function getTopology(id: string) {
Number(depth.value) Number(depth.value)
); );
break; break;
case EntityType[4].value: case EntityType[3].value:
resp = await topologyStore.getInstanceTopology(); resp = await topologyStore.getInstanceTopology();
break; break;
} }
@ -202,20 +219,36 @@ function handleClick(event: any) {
topologyStore.setLink(null); topologyStore.setLink(null);
} }
} }
watch(
() => [selectorStore.currentPod],
() => {
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
topologyStore.setLink(null);
}
);
watch(
() => appStore.durationTime,
() => {
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
topologyStore.setLink(null);
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.sankey { .sankey {
margin-top: 10px; margin-top: 10px;
background-color: #333840; background-color: #333840 !important;
color: #ddd; color: #ddd;
} }
.settings { .settings {
position: absolute; position: absolute;
top: 40px; top: 60px;
right: 0; right: 10px;
width: 400px; width: 400px;
height: 700px; height: 500px;
background-color: #2b3037; background-color: #2b3037;
overflow: auto; overflow: auto;
padding: 0 15px; padding: 0 15px;
@ -228,7 +261,8 @@ function handleClick(event: any) {
.tool { .tool {
text-align: right; text-align: right;
margin-top: 10px; margin-top: 40px;
margin-right: 10px;
position: relative; position: relative;
} }

View File

@ -28,14 +28,14 @@ export default function topoLegend(
.attr("width", 30) .attr("width", 30)
.attr("height", 30) .attr("height", 30)
.attr("x", clientWidth - (item === "CUBEERROR" ? 340 : 440)) .attr("x", clientWidth - (item === "CUBEERROR" ? 340 : 440))
.attr("y", clientHeight - 50) .attr("y", clientHeight + 50)
.attr("xlink:href", () => .attr("xlink:href", () =>
item === "CUBEERROR" ? icons.CUBEERROR : icons.CUBE item === "CUBEERROR" ? icons.CUBEERROR : icons.CUBE
); );
graph graph
.append("text") .append("text")
.attr("x", clientWidth - (item === "CUBEERROR" ? 310 : 410)) .attr("x", clientWidth - (item === "CUBEERROR" ? 310 : 410))
.attr("y", clientHeight - 30) .attr("y", clientHeight + 70)
.text(() => { .text(() => {
const l = config || []; const l = config || [];
const str = l const str = l

View File

@ -210,7 +210,16 @@ function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
tagsMap.value = data.tagsMap; tagsMap.value = data.tagsMap;
} }
watch( watch(
() => selectorStore.currentService, () => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
init();
}
);
watch(
() => [selectorStore.currentService],
() => { () => {
if (dashboardStore.entity !== EntityType[0].value) { if (dashboardStore.entity !== EntityType[0].value) {
return; return;
@ -218,6 +227,14 @@ watch(
init(); init();
} }
); );
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.inputs { .inputs {