feat: Implement templates for dashboards (#28)

This commit is contained in:
Fine0830
2022-03-19 12:11:35 +08:00
committed by GitHub
parent 1cf3887675
commit 597e98e291
61 changed files with 1583 additions and 1193 deletions

101
src/views/Layer.vue Normal file
View File

@@ -0,0 +1,101 @@
<!-- 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>
<Edit v-if="dashboardStore.currentDashboard" />
<div class="no-root" v-else>{{ t("noRoot") }} {{ layer }}</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { EntityType } from "./dashboard/data";
import { useDashboardStore } from "@/store/modules/dashboard";
import Edit from "./dashboard/Edit.vue";
const { t } = useI18n();
const route = useRoute();
const dashboardStore = useDashboardStore();
const routeNames = [
"GeneralServices",
"Database",
"MeshServices",
"ControlPanel",
"DataPanel",
];
const layer = ref<string>("GENERAL");
getDashboard();
async function getDashboard() {
dashboardStore.setCurrentDashboard(null);
setLayer(String(route.name));
await dashboardStore.setDashboards();
const index = dashboardStore.dashboards.findIndex(
(d: { name: string; isRoot: boolean; layer: string; entity: string }) =>
d.layer === layer.value && d.entity === EntityType[1].value && d.isRoot
);
if (index < 0) {
return;
}
const d = dashboardStore.dashboards[index];
dashboardStore.setCurrentDashboard(d);
}
function setLayer(n: string) {
switch (n) {
case routeNames[0]:
layer.value = "GENERAL";
break;
case routeNames[1]:
layer.value = "VIRTUAL_DATABASE";
break;
case routeNames[2]:
layer.value = "MESH";
break;
case routeNames[3]:
layer.value = "MESH_CP";
break;
case routeNames[4]:
layer.value = "MESH_DP";
break;
default:
layer.value = "GENERAL";
break;
}
dashboardStore.setLayer(layer.value);
dashboardStore.setEntity(EntityType[1].value);
// appStore.setPageTitle(layer.value);
}
watch(
() => route.name,
(name: unknown) => {
if (!name) {
return;
}
getDashboard();
}
);
</script>
<style lang="scss" scoped>
.no-root {
padding: 15px;
width: 100%;
text-align: center;
color: #888;
}
.layer {
height: 100%;
}
</style>

View File

@@ -76,7 +76,6 @@ import { ref, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
import { ElSwitch } from "element-plus";
const { t, locale } = useI18n();
const state = reactive<{ timer: ReturnType<typeof setInterval> | null }>({

View File

@@ -65,7 +65,6 @@ const tagsList = ref<string[]>([]);
function removeTags(index: number) {
tagsList.value.splice(index, 1);
updateTags();
localStorage.setItem("traceTags", JSON.stringify(this.tagsList));
}
function addLabels() {
if (!tags.value) {

View File

@@ -13,8 +13,12 @@ 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>
<Tool />
<div class="ds-main" @click="handleClick">
<Tool v-if="p.entity" />
<div
class="ds-main"
@click="handleClick"
:style="{ height: p.entity ? 'calc(100% - 45px)' : '100%' }"
>
<grid-layout />
<el-dialog
v-model="dashboardStore.showConfig"
@@ -38,9 +42,10 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import GridLayout from "./panel/Layout.vue";
// import { LayoutConfig } from "@/types/dashboard";
import Tool from "./panel/Tool.vue";
import Widget from "./configuration/Widget.vue";
import TopologyConfig from "./configuration/Topology.vue";
@@ -51,26 +56,34 @@ import { useAppStoreWithOut } from "@/store/modules/app";
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
// fetch layout data from serve side
// const layout: any[] = [
// { x: 0, y: 0, w: 4, h: 12, i: "0" },
// { x: 4, y: 0, w: 4, h: 12, i: "1" },
// { x: 8, y: 0, w: 4, h: 15, i: "2" },
// { x: 12, y: 0, w: 4, h: 9, i: "3" },
// { x: 16, y: 0, w: 4, h: 9, i: "4" },
// { x: 20, y: 0, w: 4, h: 9, i: "5" },
// { x: 0, y: 12, w: 4, h: 15, i: "7" },
// { x: 4, y: 12, w: 4, h: 15, i: "8" },
// { x: 8, y: 15, w: 4, h: 12, i: "9" },
// { x: 12, y: 9, w: 4, h: 12, i: "10" },
// { x: 16, y: 9, w: 4, h: 12, i: "11" },
// { x: 20, y: 9, w: 4, h: 15, i: "12" },
// { x: 0, y: 27, w: 4, h: 12, i: "14" },
// { x: 4, y: 27, w: 4, h: 12, i: "15" },
// { x: 8, y: 27, w: 4, h: 15, i: "16" },
// ];
// dashboardStore.setLayout(layout);
appStore.setPageTitle("Dashboard Name");
const p = useRoute().params;
const layoutKey = ref<string>(`${p.layerId}_${p.entity}_${p.name}`);
setTemplate();
async function setTemplate() {
await dashboardStore.setDashboards();
if (!p.entity) {
const { layer, entity, name } = dashboardStore.currentDashboard;
layoutKey.value = `${layer}_${entity}_${name.split(" ").join("-")}`;
}
const c: { configuration: string; id: string } = JSON.parse(
sessionStorage.getItem(layoutKey.value) || "{}"
);
const layout: any = c.configuration || {};
dashboardStore.setLayout(layout.children || []);
appStore.setPageTitle(layout.name);
if (!dashboardStore.currentDashboard) {
dashboardStore.setCurrentDashboard({
layer: p.layerId,
entity: p.entity,
name: String(p.name).split("-").join(" "),
id: c.id,
isRoot: layout.isRoot,
});
}
}
function handleClick(e: any) {
e.stopPropagation();
if (e.target.className === "ds-main") {
@@ -81,18 +94,6 @@ function handleClick(e: any) {
</script>
<style lang="scss" scoped>
.ds-main {
height: calc(100% - 45px);
overflow: auto;
}
.layout {
height: 100%;
flex-grow: 2;
overflow: hidden;
}
.grids {
height: 100%;
overflow-y: auto;
}
</style>

View File

@@ -17,13 +17,14 @@ limitations under the License. -->
<div class="flex-h header" style="margin: 10px 0">
<el-input
v-model="searchText"
placeholder="Please input"
placeholder="Please input name"
class="input-with-search"
size="small"
@change="searchDashboards"
>
<template #append>
<el-button size="small">
<Icon size="lg" iconName="search" />
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
@@ -33,38 +34,106 @@ limitations under the License. -->
</el-button>
</router-link>
</div>
<el-table :data="tableData" style="width: 100%" max-height="550">
<el-table-column fixed prop="name" label="Name" />
<el-table-column prop="type" label="Type" />
<el-table-column prop="date" label="Date" />
<el-table-column label="Operations">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">
{{ t("view") }}
</el-button>
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">
{{ t("edit") }}
</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>
{{ t("delete") }}
</el-button>
</template>
</el-table-column>
</el-table>
<div class="table">
<el-table
:data="dashboards"
:style="{ width: '100%', fontSize: '13px' }"
v-loading="loading"
ref="multipleTableRef"
:default-sort="{ prop: 'name' }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="Name" />
<el-table-column prop="layer" label="Layer" width="200" />
<el-table-column prop="entity" label="Entity" width="200" />
<el-table-column prop="isRoot" label="Root" width="100">
<template #default="scope">
<span>
{{ scope.row.isRoot ? t("yes") : t("no") }}
</span>
</template>
</el-table-column>
<el-table-column label="Operations">
<template #default="scope">
<el-button size="small" @click="handleView(scope.row)">
{{ t("view") }}
</el-button>
<el-button size="small" @click="handleEdit(scope.row)">
{{ t("edit") }}
</el-button>
<el-popconfirm
title="Are you sure to delete this?"
@confirm="handleDelete(scope.row)"
>
<template #reference>
<el-button size="small" type="danger">
{{ t("delete") }}
</el-button>
</template>
</el-popconfirm>
<el-popconfirm
title="Are you sure to set this?"
@confirm="setRoot(scope.row)"
v-if="scope.row.entity === EntityType[1].value"
>
<template #reference>
<el-button size="small" style="width: 120px" type="danger">
{{ scope.row.isRoot ? t("setNormal") : t("setRoot") }}
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="toggle-selection">
<el-button size="default" class="btn" @click="exportTemplates">
<Icon class="mr-5" iconName="save_alt" />
{{ t("export") }}
</el-button>
<el-button class="ml-10 btn" size="default">
<input
ref="dashboardFile"
id="dashboard-file"
class="import-template"
type="file"
name="file"
title=""
accept=".json"
@change="importTemplates"
/>
<label for="dashboard-file" class="input-label">
<Icon class="mr-5" iconName="folder_open" />
{{ t("import") }}
</label>
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElTable, ElTableColumn, ElButton, ElInput } from "element-plus";
import { ElMessageBox, ElMessage } from "element-plus";
import type { ElTable } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import router from "@/router";
import { DashboardItem } from "@/types/dashboard";
import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data";
import { findLastKey } from "lodash";
/*global Nullable*/
const { t } = useI18n();
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Dashboard List");
const dashboardStore = useDashboardStore();
const dashboards = ref<DashboardItem[]>([]);
const searchText = ref<string>("");
const loading = ref<boolean>(false);
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
// # - os-linux
// # - k8s
// # - general(agent-installed)
@@ -76,36 +145,218 @@ appStore.setPageTitle("Dashboard List");
// # - cache
// # - browser
// # - skywalking
const { t } = useI18n();
const searchText = ref<string>("");
const tableData = [
{
date: "2016-05-03",
name: "xxx",
type: "general",
},
{
date: "2016-05-02",
name: "xxx",
type: "k8s",
},
{
date: "2016-05-04",
name: "xxx",
type: "database",
},
{
date: "2016-05-01",
name: "xxx",
type: "mesh",
},
];
const handleEdit = (index: number, row: any) => {
console.log(index, row);
};
const handleDelete = (index: number, row: any) => {
console.log(index, row);
appStore.setPageTitle("Dashboard List");
const handleSelectionChange = (val: DashboardItem[]) => {
multipleSelection.value = val;
};
setList();
async function setList() {
await dashboardStore.setDashboards();
dashboards.value = dashboardStore.dashboards;
}
async function importTemplates(event: any) {
const arr: any = await readFile(event);
for (const item of arr) {
const { layer, name, entity } = item.configuration;
const index = dashboardStore.dashboards.findIndex(
(d: DashboardItem) =>
d.name === name && d.entity === entity && d.layer === layer && !item.id
);
if (index > -1) {
return ElMessage.error(t("nameError"));
}
}
loading.value = true;
for (const item of arr) {
const { layer, name, entity, isRoot, children } = item.configuration;
const index = dashboardStore.dashboards.findIndex(
(d: DashboardItem) => d.id === item.id
);
const p: DashboardItem = {
name: name,
layer: layer,
entity: entity,
isRoot: false,
};
if (index > -1) {
p.id = item.id;
p.isRoot = isRoot;
}
dashboardStore.setCurrentDashboard(p);
dashboardStore.setLayout(children);
await dashboardStore.saveDashboard();
}
dashboards.value = dashboardStore.dashboards;
loading.value = false;
dashboardFile.value = null;
}
function exportTemplates() {
const arr = multipleSelection.value.sort(
(a: DashboardItem, b: DashboardItem) => {
return a.name.localeCompare(b.name);
}
);
const templates = arr.map((d: DashboardItem) => {
const key = [d.layer, d.entity, d.name.split(" ").join("-")].join("_");
const layout = JSON.parse(sessionStorage.getItem(key) || "{}");
return layout;
});
const name = `dashboards.json`;
saveFile(templates, name);
setTimeout(() => {
multipleTableRef.value!.clearSelection();
}, 2000);
}
function handleView(row: DashboardItem) {
dashboardStore.setCurrentDashboard(row);
router.push(
`/dashboard/${row.layer}/${row.entity}/${row.name.split(" ").join("-")}`
);
}
async function setRoot(row: DashboardItem) {
const items: DashboardItem[] = [];
loading.value = true;
for (const d of dashboardStore.dashboards) {
if (d.id === row.id) {
d.isRoot = !row.isRoot;
const key = [d.layer, d.entity, d.name.split(" ").join("-")].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
delete c.id;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
})
);
}
} else {
if (
d.layer === row.layer &&
d.entity === row.entity &&
row.isRoot === false &&
d.isRoot === true
) {
d.isRoot = false;
const key = [d.layer, d.entity, d.name.split(" ").join("-")].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
})
);
}
}
}
items.push(d);
}
dashboardStore.resetDashboards(items);
searchDashboards();
loading.value = false;
}
function handleEdit(row: DashboardItem) {
ElMessageBox.prompt("Please input dashboard name", "Edit", {
confirmButtonText: "OK",
cancelButtonText: "Cancel",
inputValue: row.name,
})
.then(({ value }) => {
updateName(row, value);
})
.catch(() => {
ElMessage({
type: "info",
message: "Input canceled",
});
});
}
async function updateName(d: DashboardItem, value: string) {
const key = [d.layer, d.entity, d.name.split(" ").join("-")].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
name: value,
};
delete c.id;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
loading.value = true;
const res = await dashboardStore.updateDashboard(setting);
loading.value = false;
if (!res.data.changeTemplate.id) {
return;
}
dashboardStore.setCurrentDashboard({
...d,
name: value,
});
dashboards.value = dashboardStore.dashboards.map((item: any) => {
if (dashboardStore.currentDashboard.id === item.id) {
item = dashboardStore.currentDashboard;
}
return item;
});
dashboardStore.resetDashboards(dashboards.value);
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(key);
const str = [
dashboardStore.currentDashboard.layer,
dashboardStore.currentDashboard.entity,
dashboardStore.currentDashboard.name.split(" ").join("-"),
].join("_");
sessionStorage.setItem(
str,
JSON.stringify({
id: d.id,
configuration: c,
})
);
searchText.value = "";
}
async function handleDelete(row: DashboardItem) {
dashboardStore.setCurrentDashboard(row);
loading.value = true;
await dashboardStore.deleteDashboard();
dashboards.value = dashboardStore.dashboards;
loading.value = false;
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(
`${row.layer}_${row.entity}_${row.name.split(" ").join("-")}`
);
}
function searchDashboards() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
dashboards.value = list.filter((d: { name: string }) =>
d.name.includes(searchText.value)
);
}
</script>
<style lang="scss" scoped>
.header {
@@ -120,4 +371,32 @@ const handleDelete = (index: number, row: any) => {
width: 300px;
margin-left: 20px;
}
.table {
padding: 20px;
background-color: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 5px;
}
.toggle-selection {
margin-top: 20px;
background-color: #fff;
}
.btn {
width: 220px;
font-size: 13px;
}
.import-template {
display: none;
}
.input-label {
line-height: 30px;
height: 30px;
width: 220px;
cursor: pointer;
}
</style>

View File

@@ -51,15 +51,17 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { reactive, onBeforeMount } from "vue";
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import router from "@/router";
import { useSelectorStore } from "@/store/modules/selectors";
import { EntityType } from "./data";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
appStore.setPageTitle("Dashboard New");
const { t } = useI18n();
const selectorStore = useSelectorStore();
@@ -69,12 +71,30 @@ const states = reactive({
entity: EntityType[0].value,
layers: [],
});
setLayers();
dashboardStore.setDashboards();
const onCreate = () => {
const index = dashboardStore.dashboards.findIndex(
(d: { name: string; entity: string; layer: string }) =>
d.name === states.name &&
states.entity === d.entity &&
states.selectedLayer === d.layer
);
if (index > -1) {
ElMessage.error(t("nameError"));
return;
}
dashboardStore.setCurrentDashboard({
name: states.name,
entity: states.entity,
layer: states.selectedLayer,
});
const name = states.name.split(" ").join("-");
const path = `/dashboard/${states.selectedLayer}/${states.entity}/${name}`;
router.push(path);
};
onBeforeMount(async () => {
async function setLayers() {
const resp = await selectorStore.fetchLayers();
if (resp.errors) {
ElMessage.error(resp.errors);
@@ -83,11 +103,11 @@ onBeforeMount(async () => {
states.layers = resp.data.layers.map((d: string) => {
return { label: d, value: d };
});
});
function changeLayer(opt: { label: string; value: string }[]) {
}
function changeLayer(opt: { label: string; value: string }[] | any) {
states.selectedLayer = opt[0].value;
}
function changeEntity(opt: { label: string; value: string }[]) {
function changeEntity(opt: { label: string; value: string }[] | any) {
states.entity = opt[0].value;
}
</script>

View File

@@ -17,6 +17,9 @@ limitations under the License. -->
<div class="graph" v-loading="loading">
<div class="header">
<span>{{ dashboardStore.selectedGrid.widget.title }}</span>
<span v-show="dashboardStore.selectedGrid.standard.unit" class="unit">
({{ dashboardStore.selectedGrid.standard.unit }})
</span>
<div class="tips" v-show="dashboardStore.selectedGrid.widget.tips">
<el-tooltip :content="dashboardStore.selectedGrid.widget.tips">
<Icon iconName="info_outline" size="sm" />
@@ -33,6 +36,8 @@ limitations under the License. -->
i: dashboardStore.selectedGrid.i,
metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes,
standard: dashboardStore.selectedGrid.standard,
isEdit: true,
}"
/>
<div v-show="!dashboardStore.selectedGrid.graph.type" class="no-data">
@@ -55,7 +60,7 @@ limitations under the License. -->
<WidgetOptions />
</el-collapse-item>
<el-collapse-item :title="t('standardOptions')" name="4">
<StandardOptions />
<StandardOptions @update="getSource" @loading="setLoading" />
</el-collapse-item>
</el-collapse>
</div>
@@ -98,7 +103,7 @@ export default defineComponent({
const loading = ref<boolean>(false);
const states = reactive<{
activeNames: string;
source: any;
source: unknown;
index: string;
visType: Option[];
}>({
@@ -209,4 +214,9 @@ export default defineComponent({
.ds-name {
margin-bottom: 10px;
}
.unit {
display: inline-block;
margin-left: 5px;
}
</style>

View File

@@ -13,14 +13,15 @@ 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 v-show="states.isTable" class="ds-name">
<div v-if="states.isList && states.dashboardList.length" class="ds-name">
<div>{{ t("dashboards") }}</div>
<el-input
v-model="states.dashboardName"
placeholder="Please input dashboard name"
<Selector
:value="states.dashboardName"
:options="states.dashboardList"
size="small"
placeholder="Please select a dashboard name"
@change="changeDashboard"
class="selectors"
size="small"
/>
</div>
<div>{{ t("metrics") }}</div>
@@ -42,13 +43,13 @@ limitations under the License. -->
:options="states.metricTypeList[index]"
size="small"
:disabled="
dashboardStore.selectedGrid.graph.type && !states.isTable && index !== 0
dashboardStore.selectedGrid.graph.type && !states.isList && index !== 0
"
@change="changeMetricType(index, $event)"
class="selectors"
/>
<span
v-show="states.isTable || states.metricTypes[0] === 'readMetricsValues'"
v-show="states.isList || states.metricTypes[0] === 'readMetricsValues'"
>
<Icon
class="cp mr-5"
@@ -88,18 +89,19 @@ import { Option } from "@/types/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import {
MetricTypes,
TableChartTypes,
ListChartTypes,
MetricCatalog,
DefaultGraphConfig,
EntityType,
ChartTypes,
PodsChartTypes,
TableEntity,
ListEntity,
} from "../../data";
import { ElMessage } from "element-plus";
import Icon from "@/components/Icon.vue";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
import { useI18n } from "vue-i18n";
import { DashboardItem } from "@/types/dashboard";
/*global defineEmits */
const { t } = useI18n();
@@ -111,26 +113,31 @@ const states = reactive<{
metricTypes: string[];
metricTypeList: Option[][];
visTypes: Option[];
isTable: boolean;
isList: boolean;
metricList: (Option & { type: string })[];
dashboardName: string;
dashboardList: (DashboardItem & { label: string; value: string })[];
}>({
metrics: metrics && metrics.length ? metrics : [""],
metricTypes: metricTypes && metricTypes.length ? metricTypes : [""],
metricTypeList: [],
visTypes: [],
isTable: false,
isList: false,
metricList: [],
dashboardName: graph.dashboardName,
dashboardList: [],
});
states.isTable = TableChartTypes.includes(graph.type);
states.isList = ListChartTypes.includes(graph.type);
states.visTypes = setVisTypes();
setDashboards();
setMetricType();
async function setMetricType(catalog?: string) {
if (states.isTable) {
catalog = catalog || TableEntity[graph.type];
const { graph } = dashboardStore.selectedGrid;
if (states.isList) {
catalog = catalog || ListEntity[graph.type];
} else {
catalog = catalog || dashboardStore.entity;
}
@@ -140,24 +147,82 @@ async function setMetricType(catalog?: string) {
return;
}
states.metricList = (json.data.metrics || []).filter(
(d: { catalog: string }) => catalog === (MetricCatalog as any)[d.catalog]
(d: { catalog: string; type: string }) => {
if (states.isList || graph.type === "Table") {
if (
d.type === "REGULAR_VALUE" &&
catalog === (MetricCatalog as any)[d.catalog]
) {
return d;
}
} else {
if (catalog === (MetricCatalog as any)[d.catalog]) {
return d;
}
}
}
);
const metrics: any = states.metricList.filter(
(d: { value: string; type: string }) => {
const metric = states.metrics.filter((m: string) => m === d.value)[0];
if (metric) {
const index = states.metrics.findIndex((m: string) => m === d.value);
if (index > -1) {
return d;
}
}
);
if (metrics.length) {
states.metrics = metrics.map((d: { value: string }) => d.value);
} else {
states.metrics = [""];
states.metricTypes = [""];
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: states.metrics,
metricTypes: states.metricTypes,
});
states.metricTypeList = [];
for (const metric of metrics) {
states.metricTypeList.push(MetricTypes[metric.type]);
if (states.metrics.includes(metric.value)) {
const arr = setMetricTypeList(metric.type);
states.metricTypeList.push(arr);
}
}
if (states.metrics && states.metrics[0]) {
queryMetrics();
} else {
emit("update", {});
}
}
function setDashboards() {
const { graph } = dashboardStore.selectedGrid;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
states.dashboardList = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
if (d.layer === dashboardStore.layerId) {
if (
(d.entity === EntityType[0].value && graph.type === "ServiceList") ||
(d.entity === EntityType[2].value && graph.type === "EndpointList") ||
(d.entity === EntityType[3].value && graph.type === "InstanceList")
) {
prev.push({
...d,
value: d.name,
label: d.name,
});
}
}
return prev;
},
[]
);
}
function setVisTypes() {
let graphs = [];
if (dashboardStore.entity === EntityType[0].value) {
@@ -168,7 +233,7 @@ function setVisTypes() {
);
} else {
graphs = ChartTypes.filter(
(d: Option) => !TableChartTypes.includes(d.value)
(d: Option) => !ListChartTypes.includes(d.value)
);
}
@@ -177,10 +242,9 @@ function setVisTypes() {
function changeChartType(item: Option) {
const graph = DefaultGraphConfig[item.value];
states.isTable = TableChartTypes.includes(graph.type);
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
states.isTable = TableChartTypes.includes(graph.type);
if (states.isTable) {
states.isList = ListChartTypes.includes(graph.type);
if (states.isList) {
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: [""],
@@ -194,12 +258,15 @@ function changeChartType(item: Option) {
EndpointList: EntityType[2].value,
ServiceList: EntityType[0].value,
};
if (catalog[graph.type]) {
setMetricType(catalog[graph.type]);
}
setMetricType(catalog[graph.type]);
setDashboards();
states.dashboardName = "";
}
function changeMetrics(index: number, arr: (Option & { type: string })[]) {
function changeMetrics(
index: number,
arr: (Option & { type: string })[] | any
) {
if (!arr.length) {
states.metricTypeList = [];
states.metricTypes = [];
@@ -212,33 +279,34 @@ function changeMetrics(index: number, arr: (Option & { type: string })[]) {
states.metrics[index] = arr[0].value;
const typeOfMetrics = arr[0].type;
states.metricTypeList[index] = MetricTypes[typeOfMetrics];
states.metricTypeList[index] = setMetricTypeList(typeOfMetrics);
states.metricTypes[index] = MetricTypes[typeOfMetrics][0].value;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
});
if (states.isTable) {
if (states.isList) {
return;
}
queryMetrics();
}
function changeMetricType(index: number, opt: Option[]) {
function changeMetricType(index: number, opt: Option[] | any) {
const metric =
states.metricList.filter(
(d: Option) => states.metrics[index] === d.value
)[0] || {};
if (states.isTable) {
const l = setMetricTypeList(metric.type);
if (states.isList) {
states.metricTypes[index] = opt[0].value;
states.metricTypeList[index] = (MetricTypes as any)[metric.type];
states.metricTypeList[index] = l;
} else {
states.metricTypes = states.metricTypes.map((d: string) => {
d = opt[0].value;
return d;
});
states.metricTypeList = states.metricTypeList.map((d: Option[]) => {
d = (MetricTypes as any)[metric.type];
d = l;
return d;
});
@@ -247,13 +315,17 @@ function changeMetricType(index: number, opt: Option[]) {
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes },
});
if (states.isTable) {
if (states.isList) {
return;
}
queryMetrics();
}
async function queryMetrics() {
const params = useQueryProcessor(states);
if (states.isList) {
return;
}
const { standard } = dashboardStore.selectedGrid;
const params = useQueryProcessor({ ...states, standard });
if (!params) {
emit("update", {});
return;
@@ -266,11 +338,12 @@ async function queryMetrics() {
ElMessage.error(json.errors);
return;
}
const source = useSourceProcessor(json, states);
const source = useSourceProcessor(json, { ...states, standard });
emit("update", source);
}
function changeDashboard() {
function changeDashboard(opt: any) {
states.dashboardName = opt[0].value;
const graph = {
...dashboardStore.selectedGrid.graph,
dashboardName: states.dashboardName,
@@ -282,7 +355,7 @@ function changeDashboard() {
}
function addMetric() {
states.metrics.push("");
if (!states.isTable) {
if (!states.isList) {
states.metricTypes.push(states.metricTypes[0]);
states.metricTypeList.push(states.metricTypeList[0]);
return;
@@ -293,6 +366,21 @@ function deleteMetric(index: number) {
states.metrics.splice(index, 1);
states.metricTypes.splice(index, 1);
}
function setMetricTypeList(type: string) {
if (type !== "REGULAR_VALUE") {
return MetricTypes[type];
}
if (states.isList || dashboardStore.selectedGrid.graph.type === "Table") {
return [
{ label: "read all values in the duration", value: "readMetricsValues" },
{
label: "read the single value in the duration",
value: "readMetricsValue",
},
];
}
return MetricTypes[type];
}
</script>
<style lang="scss" scoped>
.ds-name {

View File

@@ -17,132 +17,148 @@ limitations under the License. -->
<span class="label">{{ t("unit") }}</span>
<el-input
class="input"
v-model="state.unit"
v-model="selectedGrid.standard.unit"
size="small"
placeholder="Please input Unit"
@change="changeStandardOpt({ unit: state.unit })"
/>
</div>
<div class="item">
<span class="label">{{ t("sortOrder") }}</span>
<Selector
:value="state.sortOrder"
:value="sortOrder"
:options="SortOrder"
size="small"
placeholder="Select a sort order"
class="selector"
@change="changeStandardOpt({ sortOrder: state.sortOrder })"
@change="changeStandardOpt({ sortOrder })"
/>
</div>
<div class="item">
<span class="label">{{ t("max") }}</span>
<div class="item" v-show="percentile">
<span class="label">{{ t("labels") }}</span>
<el-input
class="input"
v-model="state.max"
v-model="selectedGrid.standard.metricLabels"
size="small"
placeholder="auto"
@change="changeStandardOpt({ max: state.max })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("min") }}</span>
<div class="item" v-show="percentile">
<span class="label">{{ t("labelsIndex") }}</span>
<el-input
class="input"
v-model="state.min"
v-model="selectedGrid.standard.labelsIndex"
size="small"
placeholder="auto"
@change="changeStandardOpt({ min: state.min })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("plus") }}</span>
<el-input
class="input"
v-model="state.plus"
v-model="selectedGrid.standard.plus"
size="small"
placeholder="none"
@change="changeStandardOpt({ plus: state.plus })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("minus") }}</span>
<el-input
class="input"
v-model="state.minus"
v-model="selectedGrid.standard.minus"
size="small"
placeholder="none"
@change="changeStandardOpt({ minus: state.minus })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("multiply") }}</span>
<el-input
class="input"
v-model="state.multiply"
v-model="selectedGrid.standard.multiply"
size="small"
placeholder="none"
@change="changeStandardOpt({ multiply: state.multiply })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("divide") }}</span>
<el-input
class="input"
v-model="state.divide"
v-model="selectedGrid.standard.divide"
size="small"
placeholder="none"
@change="changeStandardOpt({ divide: state.divide })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("convertToMilliseconds") }}</span>
<el-input
class="input"
v-model="state.milliseconds"
v-model="selectedGrid.standard.milliseconds"
size="small"
placeholder="none"
@change="changeStandardOpt({ milliseconds: state.milliseconds })"
@change="changeStandardOpt"
/>
</div>
<div class="item">
<span class="label">{{ t("convertToSeconds") }}</span>
<el-input
class="input"
v-model="state.seconds"
v-model="selectedGrid.standard.seconds"
size="small"
placeholder="none"
@change="changeStandardOpt({ seconds: state.seconds })"
@change="changeStandardOpt"
/>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { SortOrder } from "../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
import { ElMessage } from "element-plus";
/*global defineEmits */
const { t } = useI18n();
const emit = defineEmits(["update", "loading"]);
const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore;
const { t } = useI18n();
const state = reactive({
unit: selectedGrid.standard.unit,
max: "",
min: "",
plus: "",
minus: "",
multiply: "",
divide: "",
milliseconds: "",
seconds: "",
sortOrder: selectedGrid.standard.sortOrder,
});
const percentile = ref<boolean>(
selectedGrid.metricTypes.includes("readLabeledMetricsValues")
);
const sortOrder = ref<string>(selectedGrid.standard.sortOrder || "DES");
function changeStandardOpt(param: { [key: string]: unknown }) {
const standard = {
...selectedGrid.standard,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, standard });
function changeStandardOpt(param?: any) {
let standard = dashboardStore.selectedGrid.standard;
if (param) {
standard = {
...dashboardStore.selectedGrid.standard,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, standard });
}
queryMetrics();
}
async function queryMetrics() {
const params = useQueryProcessor(dashboardStore.selectedGrid);
if (!params) {
emit("update", {});
return;
}
emit("loading", true);
const json = await dashboardStore.fetchMetricValue(params);
emit("loading", false);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const source = useSourceProcessor(json, dashboardStore.selectedGrid);
emit("update", source);
}
</script>
<style lang="scss" scoped>

View File

@@ -20,8 +20,8 @@ limitations under the License. -->
v-model="fontSize"
show-input
input-size="small"
:min="10"
:max="20"
:min="12"
:max="50"
:step="1"
@change="updateConfig({ fontSize })"
/>

View File

@@ -13,6 +13,24 @@ 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>
<span class="label">{{ t("showXAxis") }}</span>
<el-switch
v-model="selectedGrid.graph.showXAxis"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ showXAxis: selectedGrid.graph.showXAxis })"
/>
</div>
<div>
<span class="label">{{ t("showYAxis") }}</span>
<el-switch
v-model="selectedGrid.graph.showYAxis"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ showYAxis: selectedGrid.graph.showYAxis })"
/>
</div>
<div>
<span class="label">{{ t("smooth") }}</span>
<el-switch

View File

@@ -13,7 +13,16 @@ 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>
<div class="item">
<span class="label">{{ t("showGroup") }}</span>
<el-switch
v-model="selectedGrid.graph.showGroup"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ showGroup: selectedGrid.graph.showGroup })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontSize") }}</span>
<el-slider
class="slider"
@@ -38,6 +47,7 @@ const { selectedGrid } = dashboardStore;
const fontSize = ref(selectedGrid.graph.fontSize);
function updateConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
@@ -57,4 +67,8 @@ function updateConfig(param: { [key: string]: unknown }) {
display: block;
margin-bottom: 5px;
}
.item {
margin-top: 5px;
}
</style>

View File

@@ -75,4 +75,8 @@ function updateConfig(param: { [key: string]: unknown }) {
display: block;
margin-bottom: 5px;
}
.item {
margin-top: 10px;
}
</style>

View File

@@ -14,7 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-wrapper flex-v">
<el-popover placement="bottom" trigger="click" :width="100">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="routeParams.entity"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -34,6 +39,7 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue";
@@ -47,6 +53,7 @@ const props = defineProps({
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const routeParams = useRoute().params;
const dashboardStore = useDashboardStore();
function removeWidget() {

View File

@@ -14,7 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-wrapper flex-v">
<el-popover placement="bottom" trigger="click" :width="100">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="routeParams.entity"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -34,6 +39,7 @@ import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/profile/Header.vue";
import Content from "../related/profile/Content.vue";
import { useRoute } from "vue-router";
/*global defineProps */
const props = defineProps({
@@ -44,6 +50,7 @@ const props = defineProps({
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const routeParams = useRoute().params;
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);

View File

@@ -34,9 +34,10 @@ limitations under the License. -->
size="sm"
iconName="cancel"
@click="deleteTabItem($event, idx)"
v-if="routeParams.entity"
/>
</span>
<span class="tab-icons">
<span class="tab-icons" v-if="routeParams.entity">
<el-tooltip content="Add tab items" placement="bottom">
<i @click="addTabItem">
<Icon size="middle" iconName="add" />
@@ -44,7 +45,7 @@ limitations under the License. -->
</el-tooltip>
</span>
</div>
<div class="operations">
<div class="operations" v-if="routeParams.entity">
<el-popover
placement="bottom"
trigger="click"
@@ -84,7 +85,6 @@ limitations under the License. -->
:row-height="10"
:is-draggable="true"
:is-resizable="true"
:responsive="true"
@layout-updated="layoutUpdatedEvent"
>
<grid-item
@@ -106,12 +106,13 @@ limitations under the License. -->
/>
</grid-item>
</grid-layout>
<div class="no-data-tips" v-else>Please add widgets.</div>
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
</div>
</template>
<script lang="ts">
import { ref, watch, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
@@ -134,6 +135,7 @@ export default defineComponent({
props,
setup(props) {
const { t } = useI18n();
const routeParams = useRoute().params;
const dashboardStore = useDashboardStore();
const activeTabIndex = ref<number>(0);
const activeTabWidget = ref<string>("");
@@ -144,9 +146,11 @@ export default defineComponent({
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
}
function clickTabs(e: Event, idx: number) {
e.stopPropagation();
@@ -169,6 +173,12 @@ export default defineComponent({
function deleteTabItem(e: Event, idx: number) {
e.stopPropagation();
dashboardStore.removeTabItem(props.data, idx);
const kids = dashboardStore.layout[l].children[0];
const arr = (kids && kids.children) || [];
dashboardStore.setCurrentTabItems(arr);
dashboardStore.activeGridItem(0);
activeTabIndex.value = 0;
needQuery.value = true;
}
function addTabItem() {
dashboardStore.addTabItem(props.data);
@@ -236,6 +246,7 @@ export default defineComponent({
needQuery,
canEditTabName,
showTools,
routeParams,
t,
};
},
@@ -256,7 +267,7 @@ export default defineComponent({
}
.tab-name {
max-width: 80px;
max-width: 130px;
height: 20px;
line-height: 20px;
outline: none;

View File

@@ -27,7 +27,12 @@ limitations under the License. -->
/>
</span>
</el-tooltip>
<el-popover placement="bottom" trigger="click" :width="100">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="routeParams.entity"
>
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -64,6 +69,7 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { Colors } from "../data";
@@ -76,6 +82,7 @@ const props = defineProps({
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const routeParams = useRoute().params;
const dashboardStore = useDashboardStore();
function editConfig() {

View File

@@ -20,7 +20,7 @@ limitations under the License. -->
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<div class="tools" @click="removeWidget" v-if="routeParams.entity">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
@@ -35,6 +35,7 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useRoute } from "vue-router";
import Filter from "../related/trace/Filter.vue";
import TraceList from "../related/trace/TraceList.vue";
import TraceDetail from "../related/trace/Detail.vue";
@@ -50,6 +51,7 @@ const props = defineProps({
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const routeParams = useRoute().params;
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);

View File

@@ -15,7 +15,14 @@ limitations under the License. -->
<template>
<div class="widget">
<div class="header flex-h">
<div>{{ data.widget?.title || "" }}</div>
<div>
<span>
{{ data.widget?.title || "" }}
</span>
<span class="unit" v-show="data.standard?.unit">
({{ data.standard?.unit }})
</span>
</div>
<div>
<el-tooltip :content="data.widget?.tips">
<span>
@@ -27,7 +34,12 @@ limitations under the License. -->
/>
</span>
</el-tooltip>
<el-popover placement="bottom" trigger="click" :width="100">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="routeParams.entity"
>
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -62,6 +74,7 @@ limitations under the License. -->
<script lang="ts">
import { toRefs, reactive, defineComponent, ref, watch } from "vue";
import type { PropType } from "vue";
import { useRoute } from "vue-router";
import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
@@ -69,7 +82,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import graphs from "../graphs";
import { useI18n } from "vue-i18n";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
import { EntityType, TableChartTypes } from "../data";
import { EntityType, ListChartTypes } from "../data";
const props = {
data: {
@@ -85,6 +98,7 @@ export default defineComponent({
props,
setup(props) {
const { t } = useI18n();
const routeParams = useRoute().params;
const loading = ref<boolean>(false);
const state = reactive<{ source: { [key: string]: unknown } }>({
source: {},
@@ -94,7 +108,11 @@ export default defineComponent({
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
if (dashboardStore.entity === EntityType[1].value || props.needQuery) {
if (
dashboardStore.entity === EntityType[1].value ||
props.needQuery ||
!dashboardStore.currentDashboard.id
) {
queryMetrics();
}
@@ -111,7 +129,12 @@ export default defineComponent({
if (!json) {
return;
}
state.source = useSourceProcessor(json, props.data);
const d = {
metrics: props.data.metrics,
metricTypes: props.data.metricTypes,
standard: props.data.standard,
};
state.source = useSourceProcessor(json, d);
}
function removeWidget() {
@@ -127,22 +150,26 @@ export default defineComponent({
}
}
watch(
() => [props.data.metricTypes, props.data.metrics],
() => [props.data.metricTypes, props.data.metrics, props.data.standard],
() => {
if (
dashboardStore.selectedGrid &&
props.data.i !== dashboardStore.selectedGrid.i
) {
if (!dashboardStore.selectedGrid) {
return;
}
if (TableChartTypes.includes(dashboardStore.selectedGrid.graph.type)) {
if (props.data.i !== dashboardStore.selectedGrid.i) {
return;
}
if (ListChartTypes.includes(dashboardStore.selectedGrid.graph.type)) {
return;
}
queryMetrics();
}
);
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
() => [
selectorStore.currentService,
selectorStore.currentDestService,
appStore.durationTime,
],
() => {
if (
dashboardStore.entity === EntityType[0].value ||
@@ -169,6 +196,7 @@ export default defineComponent({
editConfig,
data,
loading,
routeParams,
t,
};
},
@@ -218,4 +246,9 @@ export default defineComponent({
text-align: center;
padding-top: 20px;
}
.unit {
display: inline-block;
margin-left: 5px;
}
</style>

View File

@@ -17,7 +17,7 @@
export const PodsChartTypes = ["EndpointList", "InstanceList"];
export const TableChartTypes = ["EndpointList", "InstanceList", "ServiceList"];
export const ListChartTypes = ["EndpointList", "InstanceList", "ServiceList"];
export const ChartTypes = [
{ label: "Bar", value: "Bar" },
@@ -87,11 +87,16 @@ export const DefaultGraphConfig: { [key: string]: any } = {
type: "EndpointList",
dashboardName: "",
fontSize: 12,
showXAxis: false,
showYAxis: false,
},
ServiceList: {
type: "ServiceList",
dashboardName: "",
fontSize: 12,
showXAxis: false,
showYAxis: false,
showGroup: true,
},
HeatMap: {
type: "HeatMap",
@@ -152,8 +157,7 @@ export const EntityType = [
},
{ value: "EndpointRelation", label: "Endpoint Relation", key: 4 },
];
export const hasTopology = ["All", "Service", "ServiceRelation", "Endpoint"];
export const TableEntity: any = {
export const ListEntity: any = {
InstanceList: EntityType[3].value,
EndpointList: EntityType[2].value,
ServiceList: EntityType[0].value,
@@ -162,18 +166,51 @@ export const SortOrder = [
{ label: "DES", value: "DES" },
{ label: "ASC", value: "ASC" },
];
export const ToolIcons = [
export const AllTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "save", content: "Apply", id: "apply" },
];
export const ServiceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Add Profile", id: "addProfile" },
{ name: "assignment", content: "Add Log", id: "addLog" },
// { name: "save_alt", content: "Export", id: "export" },
// { name: "folder_open", content: "Import", id: "import" },
// { name: "settings", content: "Settings", id: "settings" },
// { name: "save", content: "Apply", id: "apply" },
{ name: "save", content: "Apply", id: "apply" },
];
export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "save", content: "Apply", id: "apply" },
];
export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "save", content: "Apply", id: "apply" },
];
export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "save", content: "Apply", id: "apply" },
];
export const PodRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "save", content: "Apply", id: "apply" },
];
export const ScopeType = [
{ value: "Service", label: "Service", key: 1 },
{ value: "Endpoint", label: "Endpoint", key: 3 },
@@ -214,4 +251,3 @@ export const QueryOrders = [
{ label: "Start Time", value: "BY_START_TIME" },
{ label: "Duration", value: "BY_DURATION" },
];
export const TraceEntitys = ["All", "Service", "ServiceInstance", "Endpoint"];

View File

@@ -16,6 +16,7 @@ limitations under the License. -->
<template>
<div
class="chart-card"
:class="{ center: config.textAlign === 'center' }"
:style="{ fontSize: `${config.fontSize}px`, textAlign: config.textAlign }"
>
{{
@@ -52,12 +53,15 @@ const singleVal = computed(() => props.data[key.value]);
</script>
<style lang="scss" scoped>
.chart-card {
box-sizing: border-box;
color: #333;
height: 100%;
}
.center {
box-sizing: border-box;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
height: 100%;
}
</style>

View File

@@ -18,54 +18,57 @@ limitations under the License. -->
<el-input
v-model="searchText"
placeholder="Please input endpoint name"
class="input-with-search"
size="small"
@change="searchList"
class="inputs"
>
<template #append>
<el-button size="small" @click="searchList">
<Icon size="lg" iconName="search" />
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
</div>
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%">
<el-table-column label="Endpoints">
<template #default="scope">
<router-link
class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${selectorStore.currentService.id}/${scope.row.id}/${config.dashboardName}`"
:style="{ fontSize: `${config.fontSize}px` }"
>
{{ scope.row.label }}
</router-link>
</template>
</el-table-column>
<el-table-column
v-for="(metric, index) in config.metrics"
:label="metric"
:key="metric + index"
>
<template #default="scope">
<div class="chart">
<Line
v-if="config.metricTypes[index] === 'readMetricsValues'"
:data="{ [metric]: scope.row[metric] }"
:intervalTime="intervalTime"
:config="{ showXAxis: false, showYAxis: false }"
/>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
</div>
</template>
</el-table-column>
</el-table>
<div class="list">
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%">
<el-table-column label="Endpoints">
<template #default="scope">
<router-link
class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${selectorStore.currentService.id}/${scope.row.id}/${config.dashboardName}`"
:style="{ fontSize: `${config.fontSize}px` }"
>
{{ scope.row.label }}
</router-link>
</template>
</el-table-column>
<el-table-column
v-for="(metric, index) in config.metrics"
:label="metric"
:key="metric + index"
>
<template #default="scope">
<div class="chart">
<Line
v-if="config.metricTypes[index] === 'readMetricsValues'"
:data="{ [metric]: scope.row[metric] }"
:intervalTime="intervalTime"
:config="{ showXAxis: false, showYAxis: false }"
/>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
</div>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
class="pagination"
background
small
layout="prev, pager, next"
:page-size="pageSize"
:total="selectorStore.pods.length"
@@ -99,6 +102,7 @@ const props = defineProps({
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
}
>,
default: () => ({ dashboardName: "", fontSize: 12, i: "" }),
@@ -126,6 +130,9 @@ async function queryEndpoints() {
}
searchEndpoints.value = selectorStore.pods;
endpoints.value = selectorStore.pods.splice(0, pageSize);
if (props.config.isEdit) {
return;
}
queryEndpointMetrics(endpoints.value);
}
async function queryEndpointMetrics(currentPods: Endpoint[]) {
@@ -134,7 +141,7 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (metrics.length && metrics[0]) {
const params = await useQueryPodsMetrics(
currentPods,
dashboardStore.selectedGrid,
props.config,
EntityType[2].value
);
const json = await dashboardStore.fetchMetricValue(params);
@@ -143,11 +150,7 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
ElMessage.error(json.errors);
return;
}
endpoints.value = usePodsSource(
currentPods,
json,
dashboardStore.selectedGrid
);
endpoints.value = usePodsSource(currentPods, json, props.config);
return;
}
endpoints.value = currentPods;
@@ -177,4 +180,8 @@ watch(
.chart {
height: 39px;
}
.inputs {
width: 300px;
}
</style>

View File

@@ -18,54 +18,57 @@ limitations under the License. -->
<el-input
v-model="searchText"
placeholder="Please input instance name"
class="input-with-search"
size="small"
@change="searchList"
class="inputs"
>
<template #append>
<el-button size="small" @click="searchList">
<Icon size="lg" iconName="search" />
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
</div>
<el-table v-loading="chartLoading" :data="instances" style="width: 100%">
<el-table-column label="Service Instances">
<template #default="scope">
<router-link
class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${selectorStore.currentService.id}/${scope.row.id}/${config.dashboardName}`"
:style="{ fontSize: `${config.fontSize}px` }"
>
{{ scope.row.label }}
</router-link>
</template>
</el-table-column>
<el-table-column
v-for="(metric, index) in config.metrics"
:label="metric"
:key="metric + index"
>
<template #default="scope">
<div class="chart">
<Line
v-if="config.metricTypes[index] === 'readMetricsValues'"
:data="metric ? { [metric]: scope.row[metric] } : {}"
:intervalTime="intervalTime"
:config="{ showXAxis: false, showYAxis: false }"
/>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
</div>
</template>
</el-table-column>
</el-table>
<div class="list">
<el-table v-loading="chartLoading" :data="instances" style="width: 100%">
<el-table-column label="Service Instances">
<template #default="scope">
<router-link
class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${selectorStore.currentService.id}/${scope.row.id}/${config.dashboardName}`"
:style="{ fontSize: `${config.fontSize}px` }"
>
{{ scope.row.label }}
</router-link>
</template>
</el-table-column>
<el-table-column
v-for="(metric, index) in config.metrics"
:label="metric"
:key="metric + index"
>
<template #default="scope">
<div class="chart">
<Line
v-if="config.metricTypes[index] === 'readMetricsValues'"
:data="metric ? { [metric]: scope.row[metric] } : {}"
:intervalTime="intervalTime"
:config="{ showXAxis: false, showYAxis: false }"
/>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
</div>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
class="pagination"
background
small
layout="prev, pager, next"
:page-size="pageSize"
:total="searchInstances.length"
@@ -96,6 +99,7 @@ const props = defineProps({
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
}
>,
default: () => ({
@@ -129,6 +133,9 @@ async function queryInstance() {
}
searchInstances.value = selectorStore.pods;
instances.value = searchInstances.value.splice(0, pageSize);
if (props.config.isEdit) {
return;
}
queryInstanceMetrics(instances.value);
}
@@ -138,7 +145,7 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
if (metrics.length && metrics[0]) {
const params = await useQueryPodsMetrics(
currentInstances,
dashboardStore.selectedGrid,
props.config,
EntityType[3].value
);
const json = await dashboardStore.fetchMetricValue(params);
@@ -147,11 +154,7 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
ElMessage.error(json.errors);
return;
}
instances.value = usePodsSource(
currentInstances,
json,
dashboardStore.selectedGrid
);
instances.value = usePodsSource(currentInstances, json, props.config);
return;
}
instances.value = currentInstances;
@@ -182,4 +185,8 @@ watch(
.chart {
height: 40px;
}
.inputs {
width: 300px;
}
</style>

View File

@@ -78,11 +78,11 @@ function getOption() {
]),
name: i,
type: "line",
symbol: "none",
barMaxWidth: 10,
symbol: "circle",
symbolSize: 8,
showSymbol: props.config.showSymbol,
step: props.config.step,
smooth: props.config.smooth,
showSymbol: true,
lineStyle: {
width: 1.5,
type: "solid",

View File

@@ -18,55 +18,72 @@ limitations under the License. -->
<el-input
v-model="searchText"
placeholder="Please input service name"
class="input-with-search"
size="small"
@change="searchList"
class="inputs mt-5"
>
<template #append>
<el-button size="small" @click="searchList">
<Icon size="lg" iconName="search" />
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
</div>
<el-table v-loading="chartLoading" :data="services" style="width: 100%">
<el-table-column label="Services">
<template #default="scope">
<router-link
class="link"
:to="`/dashboard/${dashboardStore.layerId}/${EntityType[0].value}/${scope.row.id}/${config.dashboardName}`"
:key="1"
:style="{ fontSize: `${config.fontSize}px` }"
>
{{ scope.row.label }}
</router-link>
</template>
</el-table-column>
<el-table-column
v-for="(metric, index) in config.metrics"
:label="metric"
:key="metric + index"
<div class="list">
<el-table
v-loading="chartLoading"
:data="services"
style="width: 100%"
:span-method="objectSpanMethod"
:border="true"
:style="{ fontSize: '14px' }"
>
<template #default="scope">
<div class="chart">
<Line
v-if="config.metricTypes[index] === 'readMetricsValues'"
:data="{ [metric]: scope.row[metric] }"
:intervalTime="intervalTime"
:config="{ showXAxis: false, showYAxis: false }"
/>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
</div>
</template>
</el-table-column>
</el-table>
<el-table-column label="Service Groups" v-if="config.showGroup">
<template #default="scope">
{{ scope.row.group }}
</template>
</el-table-column>
<el-table-column label="Service Names">
<template #default="scope">
<router-link
class="link"
:to="`/dashboard/${dashboardStore.layerId}/${
EntityType[0].value
}/${scope.row.id}/${config.dashboardName.split(' ').join('-')}`"
:key="1"
:style="{ fontSize: `${config.fontSize}px` }"
>
{{ scope.row.label }}
</router-link>
</template>
</el-table-column>
<el-table-column
v-for="(metric, index) in config.metrics"
:label="metric"
:key="metric + index"
>
<template #default="scope">
<div class="chart">
<Line
v-if="config.metricTypes[index] === 'readMetricsValues'"
:data="{ [metric]: scope.row[metric] }"
:intervalTime="intervalTime"
:config="{ showXAxis: false, showYAxis: false }"
/>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
</div>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
class="pagination"
background
small
layout="prev, pager, next"
:page-size="pageSize"
:total="selectorStore.services.length"
@@ -100,6 +117,7 @@ const props = defineProps({
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
}
>,
default: () => ({ dashboardName: "", fontSize: 12 }),
@@ -113,6 +131,7 @@ const pageSize = 5;
const services = ref<Service[]>([]);
const searchServices = ref<Service[]>([]);
const searchText = ref<string>("");
const groups = ref<any>({});
queryServices();
@@ -124,7 +143,34 @@ async function queryServices() {
if (resp.errors) {
ElMessage.error(resp.errors);
}
services.value = selectorStore.services.splice(0, pageSize);
const map: { [key: string]: any[] } = selectorStore.services.reduce(
(result: { [key: string]: any[] }, item: any) => {
item.group = item.group || "";
if (result[item.group]) {
item.merge = true;
} else {
item.merge = false;
result[item.group] = [];
}
result[item.group].push(item);
return result;
},
{}
);
services.value = Object.values(map).flat(1).splice(0, pageSize);
const obj = {} as any;
for (const s of services.value) {
s.group = s.group || "";
if (!obj[s.group]) {
obj[s.group] = 1;
} else {
obj[s.group]++;
}
groups.value[s.group] = obj[s.group];
}
if (props.config.isEdit) {
return;
}
queryServiceMetrics(services.value);
}
async function queryServiceMetrics(currentServices: Service[]) {
@@ -133,7 +179,7 @@ async function queryServiceMetrics(currentServices: Service[]) {
if (metrics.length && metrics[0]) {
const params = await useQueryPodsMetrics(
currentServices,
dashboardStore.selectedGrid,
props.config,
EntityType[0].value
);
const json = await dashboardStore.fetchMetricValue(params);
@@ -142,15 +188,27 @@ async function queryServiceMetrics(currentServices: Service[]) {
ElMessage.error(json.errors);
return;
}
services.value = usePodsSource(
currentServices,
json,
dashboardStore.selectedGrid
);
services.value = usePodsSource(currentServices, json, props.config);
return;
}
services.value = currentServices;
}
function objectSpanMethod(param: any): any {
if (!props.config.showGroup) {
return;
}
if (param.columnIndex !== 0) {
return;
}
if (param.row.merge) {
return {
rowspan: 0,
colspan: 0,
};
} else {
return { rowspan: groups.value[param.row.group], colspan: 1 };
}
}
function changePage(pageIndex: number) {
services.value = selectorStore.services.splice(pageIndex - 1, pageSize);
}
@@ -175,4 +233,8 @@ watch(
.chart {
height: 39px;
}
.inputs {
width: 300px;
}
</style>

View File

@@ -21,13 +21,13 @@ limitations under the License. -->
:style="`width: ${nameWidth + initWidth}px`"
>
<div class="name" :style="`width: ${nameWidth}px`">
{{ config.tableHeaderCol1 || $t("name") }}
{{ config.graph.tableHeaderCol1 || t("name") }}
<i class="r cp" ref="draggerName">
<Icon iconName="settings_ethernet" size="middle" />
</i>
</div>
<div class="value-col" v-if="config.showTableValues">
{{ config.tableHeaderCol2 || $t("value") }}
<div class="value-col" v-if="showTableValues">
{{ config.graph.tableHeaderCol2 || t("value") }}
</div>
</div>
<div
@@ -37,8 +37,12 @@ limitations under the License. -->
:style="`width: ${nameWidth + initWidth}px`"
>
<div :style="`width: ${nameWidth}px`">{{ key }}</div>
<div class="value-col" v-if="config.showTableValues">
{{ data[key][data[key].length - 1 || 0] }}
<div class="value-col" v-if="showTableValues">
{{
config.metricTypes[0] === "readMetricsValue"
? data[key]
: data[key][data[key].length - 1 || 0]
}}
</div>
</div>
</div>
@@ -47,6 +51,7 @@ limitations under the License. -->
<script lang="ts" setup>
import { computed, ref, onMounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
/*global defineProps */
const props = defineProps({
data: {
@@ -55,26 +60,32 @@ const props = defineProps({
},
config: {
type: Object as PropType<{
showTableValues: boolean;
tableHeaderCol2: string;
tableHeaderCol1: string;
graph: {
showTableValues: boolean;
tableHeaderCol2: string;
tableHeaderCol1: string;
};
metricTypes: string[];
}>,
default: () => ({}),
default: () => ({ showTableValues: true }),
},
});
/*global Nullable*/
const { t } = useI18n();
const chartTable = ref<Nullable<HTMLElement>>(null);
const initWidth = ref<number>(0);
const nameWidth = ref<number>(0);
const draggerName = ref<Nullable<HTMLElement>>(null);
const showTableValues = ref<boolean>(props.config.graph.showTableValues);
onMounted(() => {
if (!chartTable.value) {
return;
}
const width = props.config.showTableValues
const width = props.config.graph.showTableValues
? chartTable.value.offsetWidth / 2
: chartTable.value.offsetWidth;
initWidth.value = props.config.showTableValues
initWidth.value = props.config.graph.showTableValues
? chartTable.value.offsetWidth / 2
: 0;
nameWidth.value = width - 5;
@@ -95,8 +106,12 @@ onMounted(() => {
};
});
const dataKeys = computed(() => {
if (props.config.metricTypes[0] === "readMetricsValue") {
const keys = Object.keys(props.data || {});
return keys;
}
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
(i: string) => Array.isArray(props.data[i]) && props.data[i].length
);
return keys;
});

View File

@@ -20,6 +20,11 @@
padding: 0 10px 5px 0;
}
.list {
margin-top: 10px;
margin-bottom: 10px;
}
.pagination {
width: 100%;
text-align: center;

View File

@@ -19,7 +19,7 @@ limitations under the License. -->
:row-height="10"
:is-draggable="true"
:is-resizable="true"
@layout-updated="layoutUpdatedEvent"
v-if="dashboardStore.layout.length"
>
<grid-item
v-for="item in dashboardStore.layout"
@@ -36,10 +36,13 @@ limitations under the License. -->
<component :is="item.type" :data="item" />
</grid-item>
</grid-layout>
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent, onBeforeUnmount } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { LayoutConfig } from "@/types/dashboard";
import controls from "../controls/index";
@@ -47,7 +50,9 @@ export default defineComponent({
name: "Layout",
components: { ...controls },
setup() {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
function layoutUpdatedEvent(newLayout: LayoutConfig[]) {
dashboardStore.setLayout(newLayout);
}
@@ -55,10 +60,16 @@ export default defineComponent({
dashboardStore.activeGridItem(item.i);
dashboardStore.selectWidget(item);
}
onBeforeUnmount(() => {
dashboardStore.setLayout([]);
selectorStore.setCurrentService(null);
selectorStore.setCurrentPod(null);
});
return {
dashboardStore,
layoutUpdatedEvent,
clickGrid,
t,
};
},
});
@@ -78,4 +89,12 @@ export default defineComponent({
.vue-grid-item.active {
border: 1px solid #409eff;
}
.no-data-tips {
width: 100%;
text-align: center;
font-size: 14px;
padding-top: 30px;
color: #888;
}
</style>

View File

@@ -75,35 +75,30 @@ limitations under the License. -->
<div class="tool-icons">
<span
@click="clickIcons(t)"
v-for="(t, index) in ToolIcons"
v-for="(t, index) in toolIcons"
:key="index"
:title="t.content"
>
<Icon
class="icon-btn"
size="sm"
:iconName="t.name"
v-if="
!['topology', 'trace', 'profile'].includes(t.id) ||
(t.id === 'topology' &&
hasTopology.includes(dashboardStore.entity)) ||
(t.id === 'trace' &&
TraceEntitys.includes(dashboardStore.entity)) ||
(t.id === 'profile' &&
dashboardStore.entity === EntityType[0].value)
"
/>
<Icon class="icon-btn" size="sm" :iconName="t.name" />
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch } from "vue";
import { reactive, ref } from "vue";
import { useRoute } from "vue-router";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType, ToolIcons, hasTopology, TraceEntitys } from "../data";
import {
EntityType,
AllTools,
ServiceTools,
InstanceTools,
EndpointTools,
PodRelationTools,
ServiceRelationTools,
} from "../data";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { Option } from "@/types/app";
@@ -113,6 +108,8 @@ const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const params = useRoute().params;
const type = EntityType.filter((d: Option) => d.value === params.entity)[0];
const toolIcons =
ref<{ name: string; content: string; id: string }[]>(PodRelationTools);
const states = reactive<{
destService: string;
destPod: string;
@@ -131,12 +128,14 @@ const states = reactive<{
currentDestPod: "",
});
dashboardStore.setLayer(String(params.layerId));
dashboardStore.setEntity(String(params.entity));
dashboardStore.setLayer(params.layerId);
dashboardStore.setEntity(params.entity);
appStore.setEventStack([initSelector]);
initSelector();
function initSelector() {
getTools();
if (params.serviceId) {
setSelector();
} else {
@@ -180,7 +179,8 @@ async function setSelector() {
selectorStore.setCurrentService(currentService);
selectorStore.setCurrentDestService(currentDestService);
states.currentService = selectorStore.currentService.value;
states.currentDestService = selectorStore.currentDestService.value;
states.currentDestService =
selectorStore.currentDestService && selectorStore.currentDestService.value;
}
async function setSourceSelector() {
@@ -318,6 +318,9 @@ function setTabControls(id: string) {
case "addTopology":
dashboardStore.addTabControls("Topology");
break;
case "apply":
dashboardStore.saveDashboard();
break;
default:
ElMessage.info("Don't support this control");
break;
@@ -344,8 +347,8 @@ function setControls(id: string) {
case "addTopology":
dashboardStore.addControl("Topology");
break;
case "settings":
dashboardStore.setConfigPanel(true);
case "apply":
dashboardStore.saveDashboard();
break;
default:
dashboardStore.addControl("Widget");
@@ -401,12 +404,27 @@ async function fetchPods(type: string, serviceId: string, setPod: boolean) {
ElMessage.error(resp.errors);
}
}
watch(
() => appStore.durationTime,
() => {
initSelector();
function getTools() {
switch (params.entity) {
case EntityType[1].value:
toolIcons.value = AllTools;
break;
case EntityType[0].value:
toolIcons.value = ServiceTools;
break;
case EntityType[2].value:
toolIcons.value = EndpointTools;
break;
case EntityType[3].value:
toolIcons.value = InstanceTools;
break;
case EntityType[4].value:
toolIcons.value = ServiceRelationTools;
break;
default:
toolIcons.value = PodRelationTools;
}
);
}
</script>
<style lang="scss" scoped>
.dashboard-tool {

View File

@@ -148,7 +148,7 @@ const state = reactive<any>({
init();
async function init() {
const resp = await logStore.queryLogsByKeywords();
const resp = await logStore.getLogsByKeywords();
if (resp.errors) {
ElMessage.error(resp.errors);

View File

@@ -83,7 +83,8 @@ searchTasks();
// }
async function searchTasks() {
profileStore.setConditions({
serviceId: selectorStore.currentService.id,
serviceId:
(selectorStore.currentService && selectorStore.currentService.id) || "",
endpointName: endpointName.value,
});
const res = await profileStore.getTaskList();

View File

@@ -1,23 +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="enpoints">This is a enpoint page</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
div {
padding: 15px;
}
</style>

View File

@@ -1,24 +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>This is the Metrics page</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
div {
padding: 15px;
text-align: center;
}
</style>

View File

@@ -1,110 +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="service-detail">
<div class="title">
<span>{{ state.serviceID }}</span>
<span>Types</span>
<span>Technologies</span>
</div>
<div class="tabs">
<router-link
class="tab cp"
v-for="tab in tabs"
:key="tab"
@click="handleClick(tab)"
:class="{ active: tab === activeName }"
:to="`${state.path}/${state.serviceID}/${tab}`"
>
{{ t(tab) }}
</router-link>
</div>
<Endpoints v-if="state.type === tabs[2]" />
<Metrics v-else-if="state.type === tabs[0]" />
<Topology
v-else-if="state.type === tabs[1]"
msg="This is the Topology page"
/>
<Traces v-else-if="state.type === tabs[3]" msg="This is the Trace page" />
<Profiles v-else msg="This is the Profiles page" />
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import Metrics from "./Metrics.vue";
import Endpoints from "./Endpoints.vue";
import Topology from "./Topology.vue";
import Traces from "./Traces.vue";
import Profiles from "./Profiles.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("General Service");
const route = useRoute();
const { t } = useI18n();
const tabs = ["metrics", "topologies", "endpoints", "traces", "profiles"];
const activeName = ref<string>(tabs[0]);
const state = reactive({
serviceID: route.params.id,
type: route.params.type,
path: route.meta.headPath,
});
function handleClick(tab: string) {
activeName.value = tab;
state.type = tab;
}
</script>
<style lang="scss" scoped>
.service-detail {
text-align: left;
}
.tabs {
padding: 15px 15px 0 15px;
border-bottom: 1px solid var(--el-border-color-light);
}
.tab {
display: inline-block;
margin-right: 30px;
font-size: 13px;
font-weight: 400;
height: 30px;
&:hover {
color: var(--el-color-primary);
}
&.active {
color: var(--el-color-primary);
border-bottom: 1px solid var(--el-color-primary);
}
}
.title {
padding: 5px 0 5px 15px;
font-size: 14px;
font-weight: 400;
border-bottom: 1px solid #dfe4e8;
background-color: #c4c8e133;
span {
display: inline-block;
margin-right: 10px;
}
}
</style>

View File

@@ -1,31 +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>{{ msg }}</div>
</template>
<script lang="ts" setup>
/*global defineProps */
defineProps({
msg: { type: String },
});
// props.msg
</script>
<style scoped>
div {
padding: 15px;
}
</style>

View File

@@ -1,131 +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="service-table">
<el-table :data="tableData" :span-method="objectSpanMethod" border>
<el-table-column
v-for="(h, index) in tableHeader"
:label="t(h)"
:key="h + index"
>
<template #default="scope">
<router-link
:to="`${state.path}/${scope.row.serviceName}/metrics`"
v-if="h === tableHeader[1] && index !== 0"
>
<span class="service-name cp">{{ scope.row[h] }}</span>
</router-link>
<span v-else>{{ scope.row[h] }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import { ElTable, ElTableColumn } from "element-plus";
const route = useRoute();
const { t } = useI18n();
const tableHeader = [
"groupName",
"serviceName",
"types",
"technologies",
"endpoints",
"health",
];
const tableData = [
{
endpoints: 2,
groupName: "group 1",
serviceName: "discount",
types: "HTTP",
health: true,
technologies: "Spring Boot",
},
{
endpoints: 3,
groupName: "group 1",
serviceName: "frontend",
types: "HTTP",
health: true,
technologies: "Node.js",
},
{
endpoints: 3,
groupName: "group 2",
serviceName: "web",
types: "",
health: true,
technologies: "Nginx",
},
{
endpoints: 3,
groupName: "group 2",
serviceName: "shipping",
types: "HTTP",
health: true,
technologies: "JVM",
},
{
endpoints: 3,
groupName: "group 3",
serviceName: "payment",
types: "HTTP MESSAGING",
health: true,
technologies: "RabbitMQ Python",
},
];
const state = reactive({
path: route.meta.headPath,
});
const objectSpanMethod = (item: { columnIndex: number; rowIndex: number }) => {
if (item.columnIndex === 0) {
if (item.rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1,
};
} else {
return {
rowspan: 0,
colspan: 0,
};
}
}
};
watch(
() => route.meta.headPath,
(path: unknown) => {
if (!path) {
return;
}
state.path = path;
}
);
</script>
<style lang="scss" scoped>
.service-name {
color: #448edf;
cursor: pointer;
}
.service-table {
padding: 15px;
}
</style>

View File

@@ -1,30 +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>Topology</div>
</template>
<script lang="ts" setup>
/*global defineProps */
defineProps({
msg: { type: String },
});
// props.msg
</script>
<style scoped>
div {
padding: 15px;
}
</style>

View File

@@ -1,31 +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>Traces</div>
</template>
<script lang="ts" setup>
/*global defineProps */
defineProps({
msg: { type: String },
});
// props.msg
</script>
<style scoped>
div {
padding: 15px;
}
</style>

View File

@@ -1,41 +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.
*/
export const TabsConfig: { [key: string]: any } = {
GeneralService: [
{ name: "metrics", path: "/generalService/metrics" },
{ name: "traces", path: "/generalService/traces" },
{ name: "profiles", path: "/generalService/profiles" },
{ name: "services", path: "/generalService" },
],
ServiceMesh: [
{ name: "services", path: "/serviceMesh" },
{ name: "metrics", path: "/serviceMesh/metrics" },
{ name: "traces", path: "/serviceMesh/traces" },
{ name: "profiles", path: "/serviceMesh/profiles" },
],
};
export const PagesConfig = [
{ label: "generalService", name: "GeneralService" },
{ label: "serviceMesh", name: "ServiceMesh" },
{ label: "virtualMachine", name: "VirtualMachine" },
{ label: "dashboardHome", name: "DashboardHome" },
{ label: "dashboardList", name: "DashboardList" },
{ label: "logs", name: "Logs" },
{ label: "settings", name: "Settings" },
{ label: "events", name: "Events" },
{ label: "alerts", name: "Alerts" },
];