This is an automated email from the ASF dual-hosted git repository.

qiuxiafan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-booster-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 767c92c  fix: polish pages and validate data (#46)
767c92c is described below

commit 767c92c60d2e4e0e6f819b450cb59bd6c3747bfa
Author: Fine0830 <[email protected]>
AuthorDate: Wed Mar 30 16:29:19 2022 +0800

    fix: polish pages and validate data (#46)
---
 src/components/Graph.vue                           | 37 ++++++++-
 src/hooks/data.ts                                  |  2 +
 src/hooks/useProcessor.ts                          | 17 ++++-
 src/locales/lang/en.ts                             |  5 +-
 src/locales/lang/zh.ts                             |  1 +
 src/store/data.ts                                  |  8 --
 src/types/dashboard.ts                             | 25 ++----
 src/views/dashboard/configuration/Text.vue         | 11 +--
 src/views/dashboard/configuration/Topology.vue     |  6 +-
 src/views/dashboard/configuration/Widget.vue       | 20 +++--
 .../configuration/widget/WidgetOptions.vue         |  7 +-
 .../configuration/widget/graph-styles/HeatMap.vue  |  3 +-
 .../configuration/widget/metric/Index.vue          | 89 +++++++++++-----------
 .../configuration/widget/metric/Standard.vue       | 14 ++--
 src/views/dashboard/controls/Widget.vue            | 51 ++++++++-----
 src/views/dashboard/data.ts                        |  4 +-
 src/views/dashboard/graphs/Bar.vue                 | 16 ++--
 src/views/dashboard/graphs/Card.vue                | 17 +++--
 src/views/dashboard/graphs/EndpointList.vue        | 38 +++------
 src/views/dashboard/graphs/HeatMap.vue             |  7 +-
 src/views/dashboard/graphs/InstanceList.vue        |  4 +-
 src/views/dashboard/graphs/Line.vue                |  2 +-
 src/views/dashboard/graphs/ServiceList.vue         |  4 +-
 src/views/dashboard/graphs/TopList.vue             | 21 ++++-
 .../related/topology/components/Graph.vue          | 16 +++-
 .../dashboard/related/topology/utils/legend.ts     |  8 +-
 26 files changed, 241 insertions(+), 192 deletions(-)

diff --git a/src/components/Graph.vue b/src/components/Graph.vue
index e70b7a1..c83f9db 100644
--- a/src/components/Graph.vue
+++ b/src/components/Graph.vue
@@ -13,10 +13,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
-  <div ref="chartRef" :style="`height:${height};width:${width};`"></div>
+  <div
+    v-if="available"
+    ref="chartRef"
+    :style="`height:${height};width:${width};`"
+  ></div>
+  <div v-else class="no-data">No Data</div>
 </template>
 <script lang="ts" setup>
-import { watch, ref, Ref, onMounted, onBeforeUnmount, unref } from "vue";
+import {
+  watch,
+  ref,
+  Ref,
+  onMounted,
+  onBeforeUnmount,
+  unref,
+  computed,
+} from "vue";
 import type { PropType } from "vue";
 import { useECharts } from "@/hooks/useEcharts";
 import { addResizeListener, removeResizeListener } from "@/utils/event";
@@ -31,11 +44,16 @@ const props = defineProps({
   height: { type: String, default: "100%" },
   width: { type: String, default: "100%" },
   option: {
-    type: Object as PropType<{ [key: string]: unknown }>,
+    type: Object as PropType<{ [key: string]: any }>,
     default: () => ({}),
   },
 });
-
+const available = computed(
+  () =>
+    Array.isArray(props.option.series) &&
+    props.option.series[0] &&
+    props.option.series[0].data
+);
 onMounted(async () => {
   await setOptions(props.option);
   chartRef.value && addResizeListener(unref(chartRef), resize);
@@ -65,3 +83,14 @@ onBeforeUnmount(() => {
   removeResizeListener(unref(chartRef), resize);
 });
 </script>
+<style lang="scss" scoped>
+.no-data {
+  font-size: 12px;
+  height: 100%;
+  box-sizing: border-box;
+  display: -webkit-box;
+  -webkit-box-orient: horizontal;
+  -webkit-box-pack: center;
+  -webkit-box-align: center;
+}
+</style>
diff --git a/src/hooks/data.ts b/src/hooks/data.ts
index 5f1fdd8..4bc394f 100644
--- a/src/hooks/data.ts
+++ b/src/hooks/data.ts
@@ -26,6 +26,8 @@ export enum MetricQueryTypes {
 export enum Calculations {
   Percentage = "percentage",
   ByteToKB = "byteToKB",
+  ByteToMB = "ByteToMB",
+  ByteToGB = "ByteToGB",
   Apdex = "apdex",
   Precision = "precision",
   ConvertSeconds = "convertSeconds",
diff --git a/src/hooks/useProcessor.ts b/src/hooks/useProcessor.ts
index e928b30..b1ed0e9 100644
--- a/src/hooks/useProcessor.ts
+++ b/src/hooks/useProcessor.ts
@@ -28,6 +28,9 @@ export function useQueryProcessor(config: any) {
   if (!(config.metrics && config.metrics[0])) {
     return;
   }
+  if (!(config.metricTypes && config.metricTypes[0])) {
+    return;
+  }
   const appStore = useAppStoreWithOut();
   const dashboardStore = useDashboardStore();
   const selectorStore = useSelectorStore();
@@ -221,13 +224,19 @@ export function useQueryPodsMetrics(
   config: { metrics: string[]; metricTypes: string[] },
   scope: string
 ) {
+  if (!(config.metrics && config.metrics[0])) {
+    return;
+  }
+  if (!(config.metricTypes && config.metricTypes[0])) {
+    return;
+  }
   const appStore = useAppStoreWithOut();
   const selectorStore = useSelectorStore();
   const conditions: { [key: string]: unknown } = {
     duration: appStore.durationTime,
   };
   const variables: string[] = [`$duration: Duration!`];
-  const { currentService } = selectorStore;
+  const currentService = selectorStore.currentService || {};
   const fragmentList = pods.map(
     (
       d: (Instance | Endpoint | Service) & { normal: boolean },
@@ -324,6 +333,12 @@ export function aggregation(val: number, config: any): 
number | string {
     case Calculations.ByteToKB:
       data = val / 1024;
       break;
+    case Calculations.ByteToMB:
+      data = val / 1024 / 1024;
+      break;
+    case Calculations.ByteToGB:
+      data = val / 1024 / 1024 / 1024;
+      break;
     case Calculations.Apdex:
       data = val / 10000;
       break;
diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts
index 609c05e..0f19874 100644
--- a/src/locales/lang/en.ts
+++ b/src/locales/lang/en.ts
@@ -38,8 +38,8 @@ const msg = {
   health: "Health",
   groupName: "Group Name",
   topologies: "Topologies",
-  dataPanel: "Data Panel",
-  controlPanel: "Control Panel",
+  dataPanel: "Data Plane",
+  controlPanel: "Control Plane",
   eventList: "Event List",
   databasePanel: "Database Panel",
   meshServicePanel: "Service Panel",
@@ -129,6 +129,7 @@ const msg = {
   textAlign: "Text Align",
   metricLabel: "Metric Label",
   showUnit: "Show Unit",
+  noGraph: "No Graph",
   hourTip: "Select Hour",
   minuteTip: "Select Minute",
   secondTip: "Select Second",
diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts
index 43f09eb..c607407 100644
--- a/src/locales/lang/zh.ts
+++ b/src/locales/lang/zh.ts
@@ -129,6 +129,7 @@ const msg = {
   textAlign: "文本对齐",
   metricLabel: "指标标签",
   showUnit: "显示单位",
+  noGraph: "无图表",
   hourTip: "选择小时",
   minuteTip: "选择分钟",
   secondTip: "选择秒数",
diff --git a/src/store/data.ts b/src/store/data.ts
index eba3c3b..d6d3e01 100644
--- a/src/store/data.ts
+++ b/src/store/data.ts
@@ -21,14 +21,6 @@ export const NewControl = {
   h: 12,
   i: "0",
   type: "Widget",
-  widget: {
-    title: "",
-  },
-  graph: {},
-  standard: {},
-  metrics: [""],
-  metricTypes: [""],
-  metricConfig: [],
 };
 export const TextConfig = {
   fontColor: "white",
diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts
index fc56a4d..d06213b 100644
--- a/src/types/dashboard.ts
+++ b/src/types/dashboard.ts
@@ -28,13 +28,12 @@ export interface LayoutConfig {
   w: number;
   h: number;
   i: string;
-  widget: WidgetConfig;
-  graph: GraphConfig;
-  standard: StandardConfig;
-  metrics: string[];
+  widget?: WidgetConfig;
+  graph?: GraphConfig;
+  metrics?: string[];
   type: string;
-  metricTypes: string[];
-  children?: any;
+  metricTypes?: string[];
+  children?: { name: string; children: LayoutConfig[] }[];
   activedTabIndex?: number;
   metricConfig?: MetricConfigOpt[];
 }
@@ -53,20 +52,6 @@ export interface WidgetConfig {
   tips?: string;
 }
 
-export interface StandardConfig {
-  sortOrder?: string;
-  unit?: string;
-  labelsIndex?: string;
-  metricLabels?: string;
-  plus?: string;
-  minus?: string;
-  multiply?: string;
-  divide?: string;
-  milliseconds?: string;
-  seconds?: string;
-  maxItemNum?: number;
-}
-
 export type GraphConfig =
   | BarConfig
   | LineConfig
diff --git a/src/views/dashboard/configuration/Text.vue 
b/src/views/dashboard/configuration/Text.vue
index 2e29815..722dfd3 100644
--- a/src/views/dashboard/configuration/Text.vue
+++ b/src/views/dashboard/configuration/Text.vue
@@ -91,12 +91,13 @@ import { useDashboardStore } from 
"@/store/modules/dashboard";
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
 const originConfig = dashboardStore.selectedGrid;
+const graph = originConfig.graph || {};
 const url = ref(originConfig.graph.url || "");
-const backgroundColor = ref(originConfig.graph.backgroundColor || "green");
-const fontColor = ref(originConfig.graph.fontColor || "white");
-const content = ref<string>(originConfig.graph.content || "");
-const fontSize = ref<number>(originConfig.graph.fontSize || 12);
-const textAlign = ref(originConfig.graph.textAlign || "left");
+const backgroundColor = ref(graph.backgroundColor || "green");
+const fontColor = ref(graph.fontColor || "white");
+const content = ref<string>(graph.content || "");
+const fontSize = ref<number>(graph.fontSize || 12);
+const textAlign = ref(graph.textAlign || "left");
 const Colors = [
   {
     label: "Green",
diff --git a/src/views/dashboard/configuration/Topology.vue 
b/src/views/dashboard/configuration/Topology.vue
index e9b2800..dac5d4d 100644
--- a/src/views/dashboard/configuration/Topology.vue
+++ b/src/views/dashboard/configuration/Topology.vue
@@ -48,9 +48,9 @@ import { Option } from "@/types/app";
 
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
-const { selectedGrid } = dashboardStore;
-const showDepth = ref<boolean>(selectedGrid.graph.showDepth);
-const depth = ref<number>(selectedGrid.graph.depth || 2);
+const graph = dashboardStore.selectedGrid.graph || {};
+const showDepth = ref<boolean>(graph.showDepth);
+const depth = ref<number>(graph.depth || 2);
 
 function applyConfig() {
   dashboardStore.setConfigs(dashboardStore.selectedGrid);
diff --git a/src/views/dashboard/configuration/Widget.vue 
b/src/views/dashboard/configuration/Widget.vue
index 13239a6..6e60c63 100644
--- a/src/views/dashboard/configuration/Widget.vue
+++ b/src/views/dashboard/configuration/Widget.vue
@@ -16,20 +16,20 @@ limitations under the License. -->
   <div class="widget-config flex-v">
     <div class="graph" v-loading="loading">
       <div class="header">
-        <span>{{ dashboardStore.selectedGrid.widget.title || "" }}</span>
-        <div class="tips" v-show="dashboardStore.selectedGrid.widget.tips">
-          <el-tooltip :content="dashboardStore.selectedGrid.widget.tips || ''">
+        <span>{{ widget.title || "" }}</span>
+        <div class="tips" v-show="widget.tips">
+          <el-tooltip :content="widget.tips || ''">
             <Icon iconName="info_outline" size="sm" />
           </el-tooltip>
         </div>
       </div>
       <div class="render-chart">
         <component
-          :is="dashboardStore.selectedGrid.graph.type"
+          :is="graph.type"
           :intervalTime="appStoreWithOut.intervalTime"
           :data="states.source"
           :config="{
-            ...dashboardStore.selectedGrid.graph,
+            ...graph,
             i: dashboardStore.selectedGrid.i,
             metrics: dashboardStore.selectedGrid.metrics,
             metricTypes: dashboardStore.selectedGrid.metricTypes,
@@ -38,7 +38,7 @@ limitations under the License. -->
           :isEdit="isEdit"
           @changeOpt="setStatus"
         />
-        <div v-show="!dashboardStore.selectedGrid.graph.type" class="no-data">
+        <div v-show="!graph.type" class="no-data">
           {{ t("noData") }}
         </div>
       </div>
@@ -56,7 +56,7 @@ limitations under the License. -->
           />
         </el-collapse-item>
         <el-collapse-item :title="t('graphStyles')" name="2">
-          <component :is="`${dashboardStore.selectedGrid.graph.type}Config`" />
+          <component :is="`${graph.type}Config`" />
         </el-collapse-item>
         <el-collapse-item :title="t('widgetOptions')" name="3">
           <WidgetOptions />
@@ -74,7 +74,7 @@ limitations under the License. -->
   </div>
 </template>
 <script lang="ts">
-import { reactive, defineComponent, ref } from "vue";
+import { reactive, defineComponent, ref, computed } from "vue";
 import { useI18n } from "vue-i18n";
 import { useDashboardStore } from "@/store/modules/dashboard";
 import { useAppStoreWithOut } from "@/store/modules/app";
@@ -111,6 +111,8 @@ export default defineComponent({
       visType: [],
     });
     const originConfig = dashboardStore.selectedGrid;
+    const widget = computed(() => dashboardStore.selectedGrid.widget || {});
+    const graph = computed(() => dashboardStore.selectedGrid.graph || {});
 
     function getSource(source: unknown) {
       states.source = source;
@@ -148,6 +150,8 @@ export default defineComponent({
       setLoading,
       setStatus,
       isEdit,
+      widget,
+      graph,
     };
   },
 });
diff --git a/src/views/dashboard/configuration/widget/WidgetOptions.vue 
b/src/views/dashboard/configuration/widget/WidgetOptions.vue
index 0d38ffe..b9d707e 100644
--- a/src/views/dashboard/configuration/widget/WidgetOptions.vue
+++ b/src/views/dashboard/configuration/widget/WidgetOptions.vue
@@ -42,12 +42,13 @@ import { useDashboardStore } from 
"@/store/modules/dashboard";
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
 const { selectedGrid } = dashboardStore;
-const title = ref<string>(selectedGrid.widget.title || "");
-const tips = ref<string>(selectedGrid.widget.tips || "");
+const widget = dashboardStore.selectedGrid.widget || {};
+const title = ref<string>(widget.title || "");
+const tips = ref<string>(widget.tips || "");
 
 function updateWidgetConfig(param: { [key: string]: unknown }) {
   const widget = {
-    ...selectedGrid.widget,
+    ...dashboardStore.selectedGrid.widget,
     ...param,
   };
   dashboardStore.selectWidget({ ...selectedGrid, widget });
diff --git a/src/views/dashboard/configuration/widget/graph-styles/HeatMap.vue 
b/src/views/dashboard/configuration/widget/graph-styles/HeatMap.vue
index de9a4da..66b10f9 100644
--- a/src/views/dashboard/configuration/widget/graph-styles/HeatMap.vue
+++ b/src/views/dashboard/configuration/widget/graph-styles/HeatMap.vue
@@ -34,7 +34,8 @@ import { useDashboardStore } from "@/store/modules/dashboard";
 
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
-const fontSize = ref(dashboardStore.selectedGrid.graph.fontSize);
+const graph = dashboardStore.selectedGrid.graph || {};
+const fontSize = ref(graph.fontSize);
 
 function updateConfig(param: { [key: string]: unknown }) {
   const graph = {
diff --git a/src/views/dashboard/configuration/widget/metric/Index.vue 
b/src/views/dashboard/configuration/widget/metric/Index.vue
index adad743..e84e7f4 100644
--- a/src/views/dashboard/configuration/widget/metric/Index.vue
+++ b/src/views/dashboard/configuration/widget/metric/Index.vue
@@ -43,9 +43,7 @@ limitations under the License. -->
       :value="states.metricTypes[index]"
       :options="states.metricTypeList[index]"
       size="small"
-      :disabled="
-        dashboardStore.selectedGrid.graph.type && !states.isList && index !== 0
-      "
+      :disabled="graph.type && !states.isList && index !== 0"
       @change="changeMetricType(index, $event)"
       class="selectors"
     />
@@ -85,11 +83,11 @@ limitations under the License. -->
   <div>{{ t("visualization") }}</div>
   <div class="chart-types">
     <span
-      v-for="(type, index) in states.visTypes"
+      v-for="(type, index) in setVisTypes"
       :key="index"
       @click="changeChartType(type)"
       :class="{
-        active: type.value === dashboardStore.selectedGrid.graph.type,
+        active: type.value === graph.type,
       }"
     >
       {{ type.label }}
@@ -97,7 +95,7 @@ limitations under the License. -->
   </div>
 </template>
 <script lang="ts" setup>
-import { reactive, ref } from "vue";
+import { reactive, ref, computed } from "vue";
 import { Option } from "@/types/app";
 import { useDashboardStore } from "@/store/modules/dashboard";
 import {
@@ -124,24 +122,26 @@ import Standard from "./Standard.vue";
 const { t } = useI18n();
 const emit = defineEmits(["update", "loading", "changeOpt"]);
 const dashboardStore = useDashboardStore();
-const { metrics, metricTypes, graph } = dashboardStore.selectedGrid;
+const metrics = computed(() => dashboardStore.selectedGrid.metrics || []);
+const graph = computed(() => dashboardStore.selectedGrid.graph || {});
+const metricTypes = computed(
+  () => dashboardStore.selectedGrid.metricTypes || []
+);
 const states = reactive<{
   metrics: string[];
   metricTypes: string[];
   metricTypeList: Option[][];
-  visTypes: Option[];
   isList: boolean;
   metricList: (Option & { type: string })[];
   dashboardName: string;
   dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
 }>({
-  metrics: metrics && metrics.length ? metrics : [""],
-  metricTypes: metricTypes && metricTypes.length ? metricTypes : [""],
+  metrics: metrics.value.length ? metrics.value : [""],
+  metricTypes: metricTypes.value.length ? metricTypes.value : [""],
   metricTypeList: [],
-  visTypes: [],
   isList: false,
   metricList: [],
-  dashboardName: graph.dashboardName,
+  dashboardName: graph.value.dashboardName,
   dashboardList: [{ label: "", value: "" }],
 });
 const currentMetricConfig = ref<MetricConfigOpt>({
@@ -152,15 +152,33 @@ const currentMetricConfig = ref<MetricConfigOpt>({
   sortOrder: "DES",
 });
 
-states.isList = ListChartTypes.includes(graph.type);
+states.isList = ListChartTypes.includes(graph.value.type);
 const defaultLen = ref<number>(states.isList ? 5 : 20);
-states.visTypes = setVisTypes();
-
 setDashboards();
 setMetricType();
 
+const setVisTypes = computed(() => {
+  let graphs = [];
+  if (dashboardStore.entity === EntityType[0].value) {
+    graphs = ChartTypes.filter(
+      (d: Option) =>
+        ![ChartTypes[7].value, ChartTypes[8].value].includes(d.value)
+    );
+  } else if (dashboardStore.entity === EntityType[1].value) {
+    graphs = ChartTypes.filter(
+      (d: Option) => !PodsChartTypes.includes(d.value)
+    );
+  } else {
+    graphs = ChartTypes.filter(
+      (d: Option) => !ListChartTypes.includes(d.value)
+    );
+  }
+
+  return graphs;
+});
+
 async function setMetricType(chart?: any) {
-  const graph = chart || dashboardStore.selectedGrid.graph;
+  const g = chart || dashboardStore.selectedGrid.graph || {};
   const json = await dashboardStore.fetchMetricList();
   if (json.errors) {
     ElMessage.error(json.errors);
@@ -172,7 +190,7 @@ async function setMetricType(chart?: any) {
         if (d.type === MetricsType.REGULAR_VALUE) {
           return d;
         }
-      } else if (graph.type === "Table") {
+      } else if (g.type === "Table") {
         if (
           d.type === MetricsType.LABELED_VALUE ||
           d.type === MetricsType.REGULAR_VALUE
@@ -203,7 +221,7 @@ async function setMetricType(chart?: any) {
     ...dashboardStore.selectedGrid,
     metrics: states.metrics,
     metricTypes: states.metricTypes,
-    graph,
+    graph: g,
   });
   states.metricTypeList = [];
   for (const metric of metrics) {
@@ -220,7 +238,7 @@ async function setMetricType(chart?: any) {
 }
 
 function setDashboards(type?: string) {
-  const graph = type || dashboardStore.selectedGrid.graph;
+  const chart = type || dashboardStore.selectedGrid.graph || {};
   const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
   const arr = list.reduce(
     (
@@ -229,9 +247,9 @@ function setDashboards(type?: string) {
     ) => {
       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")
+          (d.entity === EntityType[0].value && chart.type === "ServiceList") ||
+          (d.entity === EntityType[2].value && chart.type === "EndpointList") 
||
+          (d.entity === EntityType[3].value && chart.type === "InstanceList")
         ) {
           prev.push({
             ...d,
@@ -248,26 +266,9 @@ function setDashboards(type?: string) {
   states.dashboardList = arr.length ? arr : [{ label: "", value: "" }];
 }
 
-function setVisTypes() {
-  let graphs = [];
-  if (dashboardStore.entity === EntityType[0].value) {
-    graphs = ChartTypes.filter((d: Option) => d.value !== ChartTypes[7].value);
-  } else if (dashboardStore.entity === EntityType[1].value) {
-    graphs = ChartTypes.filter(
-      (d: Option) => !PodsChartTypes.includes(d.value)
-    );
-  } else {
-    graphs = ChartTypes.filter(
-      (d: Option) => !ListChartTypes.includes(d.value)
-    );
-  }
-
-  return graphs;
-}
-
 function changeChartType(item: Option) {
-  const graph = DefaultGraphConfig[item.value];
-  states.isList = ListChartTypes.includes(graph.type);
+  const chart = DefaultGraphConfig[item.value] || {};
+  states.isList = ListChartTypes.includes(chart.type);
   if (states.isList) {
     dashboardStore.selectWidget({
       ...dashboardStore.selectedGrid,
@@ -278,8 +279,8 @@ function changeChartType(item: Option) {
     states.metricTypes = [""];
     defaultLen.value = 5;
   }
-  setMetricType(graph);
-  setDashboards(graph.type);
+  setMetricType(chart);
+  setDashboards(chart.type);
   states.dashboardName = "";
   defaultLen.value = 10;
 }
@@ -415,7 +416,7 @@ function setMetricTypeList(type: string) {
   if (type !== MetricsType.REGULAR_VALUE) {
     return MetricTypes[type];
   }
-  if (states.isList || dashboardStore.selectedGrid.graph.type === "Table") {
+  if (states.isList || graph.value.type === "Table") {
     return [
       { label: "read all values in the duration", value: "readMetricsValues" },
       {
diff --git a/src/views/dashboard/configuration/widget/metric/Standard.vue 
b/src/views/dashboard/configuration/widget/metric/Standard.vue
index 6f873d8..046a938 100644
--- a/src/views/dashboard/configuration/widget/metric/Standard.vue
+++ b/src/views/dashboard/configuration/widget/metric/Standard.vue
@@ -56,7 +56,7 @@ limitations under the License. -->
         :clearable="true"
       />
     </div>
-    <div class="item" v-show="isTopn">
+    <div class="item mb-10" v-show="isTopn">
       <span class="label">{{ t("sortOrder") }}</span>
       <SelectSingle
         :value="currentMetric.sortOrder || 'DES'"
@@ -67,11 +67,12 @@ limitations under the License. -->
     </div>
     <div class="item" v-show="isTopn">
       <span class="label">{{ t("maxItemNum") }}</span>
-      <el-input-number
+      <el-input
         class="selectors"
         v-model="currentMetric.topN"
         size="small"
         placeholder="none"
+        type="number"
         :min="1"
         :max="100"
         @change="changeConfigs(index, { topN: currentMetric.topN || 10 })"
@@ -102,13 +103,10 @@ const currentMetric = ref<MetricConfigOpt>({
   ...props.currentMetricConfig,
   topN: props.currentMetricConfig.topN || 10,
 });
-const metricType = ref<string>(
-  dashboardStore.selectedGrid.metricTypes[props.index]
-);
+const metricTypes = dashboardStore.selectedGrid.metricTypes || [];
+const metricType = ref<string>(metricTypes[props.index]);
 const isTopn = computed(() =>
-  ["sortMetrics", "readSampledRecords"].includes(
-    dashboardStore.selectedGrid.metricTypes[props.index]
-  )
+  ["sortMetrics", "readSampledRecords"].includes(metricTypes[props.index])
 );
 function changeConfigs(
   index: number,
diff --git a/src/views/dashboard/controls/Widget.vue 
b/src/views/dashboard/controls/Widget.vue
index 511f59c..29c5079 100644
--- a/src/views/dashboard/controls/Widget.vue
+++ b/src/views/dashboard/controls/Widget.vue
@@ -17,17 +17,17 @@ limitations under the License. -->
     <div class="header flex-h">
       <div>
         <span>
-          {{ data.widget?.title || "" }}
+          {{ widget.title || "" }}
         </span>
       </div>
       <div>
-        <el-tooltip :content="data.widget?.tips">
+        <el-tooltip :content="widget.tips || ''">
           <span>
             <Icon
               iconName="info_outline"
               size="sm"
               class="operation"
-              v-show="data.widget?.tips"
+              v-show="widget.tips"
             />
           </span>
         </el-tooltip>
@@ -51,26 +51,26 @@ limitations under the License. -->
         </el-popover>
       </div>
     </div>
-    <div class="body" v-if="data.graph?.type" v-loading="loading">
+    <div class="body" v-if="graph.type" v-loading="loading">
       <component
-        :is="data.graph.type"
+        :is="graph.type"
         :intervalTime="appStore.intervalTime"
         :data="state.source"
         :config="{
           ...data.graph,
-          metrics: data.metrics,
-          metricTypes: data.metricTypes,
+          metrics: data.metrics || [''],
+          metricTypes: data.metricTypes || [''],
           i: data.i,
           metricConfig: data.metricConfig,
         }"
         :needQuery="needQuery"
       />
     </div>
-    <div v-else class="no-data">{{ t("noData") }}</div>
+    <div v-else class="no-data">{{ t("noGraph") }}</div>
   </div>
 </template>
 <script lang="ts">
-import { toRefs, reactive, defineComponent, ref, watch } from "vue";
+import { toRefs, reactive, defineComponent, ref, watch, computed } from "vue";
 import type { PropType } from "vue";
 import { LayoutConfig } from "@/types/dashboard";
 import { useDashboardStore } from "@/store/modules/dashboard";
@@ -88,7 +88,7 @@ import { EntityType, ListChartTypes } from "../data";
 const props = {
   data: {
     type: Object as PropType<LayoutConfig>,
-    default: () => ({ widget: {} }),
+    default: () => ({ widget: {}, graph: {} }),
   },
   activeIndex: { type: String, default: "" },
   needQuery: { type: Boolean, default: false },
@@ -107,14 +107,19 @@ export default defineComponent({
     const appStore = useAppStoreWithOut();
     const dashboardStore = useDashboardStore();
     const selectorStore = useSelectorStore();
-    const isList = ListChartTypes.includes(props.data.graph.type || "");
+    const graph = computed(() => props.data.graph || {});
+    const widget = computed(() => props.data.widget || {});
+    const isList = ListChartTypes.includes(
+      (props.data.graph && props.data.graph.type) || ""
+    );
 
     if ((props.needQuery || !dashboardStore.currentDashboard.id) && !isList) {
       queryMetrics();
     }
 
     async function queryMetrics() {
-      const { metricTypes, metrics } = props.data;
+      const metricTypes = props.data.metricTypes || [];
+      const metrics = props.data.metrics || [];
       const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
       const params = await useQueryProcessor({ ...props.data, catalog });
 
@@ -129,8 +134,8 @@ export default defineComponent({
         return;
       }
       const d = {
-        metrics: props.data.metrics,
-        metricTypes: props.data.metricTypes,
+        metrics: props.data.metrics || [],
+        metricTypes: props.data.metricTypes || [],
         metricConfig: props.data.metricConfig || [],
       };
       state.source = useSourceProcessor(json, d);
@@ -149,7 +154,7 @@ export default defineComponent({
       }
     }
     watch(
-      () => [props.data.metricTypes, props.data.metrics, props.data.standard],
+      () => [props.data.metricTypes, props.data.metrics],
       () => {
         if (!dashboardStore.selectedGrid) {
           return;
@@ -157,11 +162,11 @@ export default defineComponent({
         if (props.data.i !== dashboardStore.selectedGrid.i) {
           return;
         }
-        const isList = ListChartTypes.includes(props.data.graph.type || "");
-        if (
-          ListChartTypes.includes(dashboardStore.selectedGrid.graph.type) ||
-          isList
-        ) {
+        const isList = ListChartTypes.includes(
+          (props.data.graph && props.data.graph.type) || ""
+        );
+        const chart = dashboardStore.selectedGrid.graph || {};
+        if (ListChartTypes.includes(chart.type) || isList) {
           return;
         }
         queryMetrics();
@@ -170,7 +175,9 @@ export default defineComponent({
     watch(
       () => [selectorStore.currentService, selectorStore.currentDestService],
       () => {
-        const isList = ListChartTypes.includes(props.data.graph.type || "");
+        const isList = ListChartTypes.includes(
+          (props.data.graph && props.data.graph.type) || ""
+        );
         if (isList) {
           return;
         }
@@ -209,6 +216,8 @@ export default defineComponent({
       loading,
       dashboardStore,
       t,
+      graph,
+      widget,
     };
   },
 });
diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts
index 9c57902..e1b0d4f 100644
--- a/src/views/dashboard/data.ts
+++ b/src/views/dashboard/data.ts
@@ -269,8 +269,10 @@ export const TextColors: { [key: string]: string } = {
 
 export const CalculationOpts = [
   { label: "Percentage", value: "percentage" },
-  { label: "Byte to KB", value: "byteToKB" },
   { label: "Apdex", value: "apdex" },
+  { label: "Byte to KB", value: "byteToKB" },
+  { label: "Byte to MB", value: "byteToMB" },
+  { label: "Byte to GB", value: "byteToGB" },
   {
     label: "Convert milliseconds to YYYY-MM-DD HH:mm:ss",
     value: "convertMilliseconds",
diff --git a/src/views/dashboard/graphs/Bar.vue 
b/src/views/dashboard/graphs/Bar.vue
index a6737b0..8a7012a 100644
--- a/src/views/dashboard/graphs/Bar.vue
+++ b/src/views/dashboard/graphs/Bar.vue
@@ -137,13 +137,15 @@ function getOption() {
     color,
     tooltip: {
       trigger: "axis",
-      // backgroundColor: "rgb(50,50,50)",
-      // textStyle: {
-      //   fontSize: 13,
-      //   color: "#ccc",
-      // },
-      // enterable: true,
-      // extraCssText: "max-height: 300px; overflow: auto;",
+      zlevel: 1000,
+      z: 60,
+      backgroundColor: "rgb(50,50,50)",
+      textStyle: {
+        fontSize: 13,
+        color: "#ccc",
+      },
+      enterable: true,
+      extraCssText: "max-height: 300px; overflow: auto; border: none",
     },
     legend: {
       type: "scroll",
diff --git a/src/views/dashboard/graphs/Card.vue 
b/src/views/dashboard/graphs/Card.vue
index b715881..2c33eaf 100644
--- a/src/views/dashboard/graphs/Card.vue
+++ b/src/views/dashboard/graphs/Card.vue
@@ -15,21 +15,17 @@ limitations under the License. -->
 
 <template>
   <div
+    v-if="!isNaN(singleVal)"
     class="chart-card"
     :class="{ center: config.textAlign === 'center' }"
     :style="{ fontSize: `${config.fontSize}px`, textAlign: config.textAlign }"
   >
-    {{
-      typeof singleVal === "string"
-        ? singleVal
-        : isNaN(singleVal)
-        ? null
-        : singleVal.toFixed(2)
-    }}
+    {{ singleVal.toFixed(2) }}
     <span v-show="config.showUnit">
       {{ metricConfig[0]?.unit }}
     </span>
   </div>
+  <div class="center no-data" v-else>No Data</div>
 </template>
 <script lang="ts" setup>
 import { computed, PropType } from "vue";
@@ -53,7 +49,7 @@ const props = defineProps({
 });
 const metricConfig = computed(() => props.config.metricConfig || []);
 const key = computed(() => Object.keys(props.data)[0]);
-const singleVal = computed(() => props.data[key.value]);
+const singleVal = computed(() => Number(props.data[key.value]));
 </script>
 <style lang="scss" scoped>
 .chart-card {
@@ -68,4 +64,9 @@ const singleVal = computed(() => props.data[key.value]);
   -webkit-box-pack: center;
   -webkit-box-align: center;
 }
+
+.no-data {
+  height: 100%;
+  color: #666;
+}
 </style>
diff --git a/src/views/dashboard/graphs/EndpointList.vue 
b/src/views/dashboard/graphs/EndpointList.vue
index fe08b8e..9101137 100644
--- a/src/views/dashboard/graphs/EndpointList.vue
+++ b/src/views/dashboard/graphs/EndpointList.vue
@@ -65,17 +65,6 @@ limitations under the License. -->
         </el-table-column>
       </el-table>
     </div>
-    <el-pagination
-      class="pagination"
-      background
-      small
-      layout="prev, pager, next"
-      :page-size="pageSize"
-      :total="selectorStore.pods.length"
-      @current-change="changePage"
-      @prev-click="changePage"
-      @next-click="changePage"
-    />
   </div>
 </template>
 <script setup lang="ts">
@@ -107,7 +96,13 @@ const props = defineProps({
         metricTypes: string[];
       } & { metricConfig: MetricConfigOpt[] }
     >,
-    default: () => ({ dashboardName: "", fontSize: 12, i: "" }),
+    default: () => ({
+      metrics: [],
+      metricTypes: [],
+      dashboardName: "",
+      fontSize: 12,
+      i: "",
+    }),
   },
   intervalTime: { type: Array as PropType<string[]>, default: () => [] },
   isEdit: { type: Boolean, default: false },
@@ -117,7 +112,6 @@ const selectorStore = useSelectorStore();
 const dashboardStore = useDashboardStore();
 const chartLoading = ref<boolean>(false);
 const endpoints = ref<Endpoint[]>([]);
-const pageSize = 10;
 const searchText = ref<string>("");
 
 queryEndpoints();
@@ -133,18 +127,16 @@ async function queryEndpoints() {
     ElMessage.error(resp.errors);
     return;
   }
-  endpoints.value = selectorStore.pods.filter(
-    (d: unknown, index: number) => index < pageSize
-  );
+  endpoints.value = selectorStore.pods;
   queryEndpointMetrics(endpoints.value);
 }
 async function queryEndpointMetrics(currentPods: Endpoint[]) {
   if (!currentPods.length) {
     return;
   }
-  const metrics = props.config.metrics.filter((d: string) => d);
-
-  if (metrics.length && metrics[0]) {
+  const metrics = (props.config.metrics || []).filter((d: string) => d);
+  const metricTypes = props.config.metricTypes || [];
+  if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
     const params = await useQueryPodsMetrics(
       currentPods,
       props.config,
@@ -180,14 +172,6 @@ function clickEndpoint(scope: any) {
     
`/dashboard/${d.layer}/${d.entity}/${selectorStore.currentService.id}/${scope.row.id}/${d.name}`
   );
 }
-function changePage(pageIndex: number) {
-  endpoints.value = selectorStore.pods.filter((d: unknown, index: number) => {
-    if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize) {
-      return d;
-    }
-  });
-  queryEndpointMetrics(endpoints.value);
-}
 async function searchList() {
   await queryEndpoints();
 }
diff --git a/src/views/dashboard/graphs/HeatMap.vue 
b/src/views/dashboard/graphs/HeatMap.vue
index 76036c6..80e1f73 100644
--- a/src/views/dashboard/graphs/HeatMap.vue
+++ b/src/views/dashboard/graphs/HeatMap.vue
@@ -18,7 +18,6 @@ limitations under the License. -->
 <script lang="ts" setup>
 import { computed } from "vue";
 import type { PropType } from "vue";
-import { StandardConfig } from "@/types/dashboard";
 
 /*global defineProps */
 const props = defineProps({
@@ -31,11 +30,7 @@ const props = defineProps({
   intervalTime: { type: Array as PropType<string[]>, default: () => [] },
   config: {
     type: Object as PropType<any>,
-    default: () => ({}),
-  },
-  standard: {
-    type: Object as PropType<StandardConfig>,
-    default: () => ({}),
+    default: () => ({ metrics: [] }),
   },
 });
 const option = computed(() => getOption());
diff --git a/src/views/dashboard/graphs/InstanceList.vue 
b/src/views/dashboard/graphs/InstanceList.vue
index ff608a6..65d76ba 100644
--- a/src/views/dashboard/graphs/InstanceList.vue
+++ b/src/views/dashboard/graphs/InstanceList.vue
@@ -166,9 +166,9 @@ async function queryInstanceMetrics(currentInstances: 
Instance[]) {
   if (!currentInstances.length) {
     return;
   }
-  const { metrics } = props.config;
+  const { metrics, metricTypes } = props.config;
 
-  if (metrics.length && metrics[0]) {
+  if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
     const params = await useQueryPodsMetrics(
       currentInstances,
       props.config,
diff --git a/src/views/dashboard/graphs/Line.vue 
b/src/views/dashboard/graphs/Line.vue
index 2df370f..978aa10 100644
--- a/src/views/dashboard/graphs/Line.vue
+++ b/src/views/dashboard/graphs/Line.vue
@@ -153,7 +153,7 @@ function getOption() {
         color: "#ccc",
       },
       enterable: true,
-      extraCssText: "max-height: 300px; overflow: auto;",
+      extraCssText: "max-height: 300px; overflow: auto; border: none",
     },
     legend: {
       type: "scroll",
diff --git a/src/views/dashboard/graphs/ServiceList.vue 
b/src/views/dashboard/graphs/ServiceList.vue
index 7bcac74..8be7cd6 100644
--- a/src/views/dashboard/graphs/ServiceList.vue
+++ b/src/views/dashboard/graphs/ServiceList.vue
@@ -210,9 +210,9 @@ async function queryServiceMetrics(currentServices: 
Service[]) {
   if (!currentServices.length) {
     return;
   }
-  const { metrics } = props.config;
+  const { metrics, metricTypes } = props.config;
 
-  if (metrics.length && metrics[0]) {
+  if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
     const params = await useQueryPodsMetrics(
       currentServices,
       props.config,
diff --git a/src/views/dashboard/graphs/TopList.vue 
b/src/views/dashboard/graphs/TopList.vue
index 0b2fc18..8fe0f72 100644
--- a/src/views/dashboard/graphs/TopList.vue
+++ b/src/views/dashboard/graphs/TopList.vue
@@ -14,7 +14,7 @@ See the License for the specific language governing 
permissions and
 limitations under the License. -->
 
 <template>
-  <div class="top-list">
+  <div class="top-list" v-if="available">
     <div class="chart-slow-i" v-for="(i, index) in data[key]" :key="index">
       <div class="ell tools flex-h">
         <div>
@@ -35,11 +35,12 @@ limitations under the License. -->
       <el-progress
         :stroke-width="6"
         :percentage="(i.value / maxValue) * 100"
-        :color="TextColors[config.color]"
+        :color="TextColors[config.color || 'purple']"
         :show-text="false"
       />
     </div>
   </div>
+  <div class="center no-data" v-else>No Data</div>
 </template>
 <script lang="ts" setup>
 import type { PropType } from "vue";
@@ -61,6 +62,12 @@ const props = defineProps({
   intervalTime: { type: Array as PropType<string[]>, default: () => [] },
 });
 const key = computed(() => Object.keys(props.data)[0] || "");
+const available = computed(
+  () =>
+    Array.isArray(props.data[key.value]) &&
+    props.data[key.value][0] &&
+    props.data[key.value][0].value
+);
 const maxValue = computed(() => {
   if (!(props.data[key.value] && props.data[key.value].length)) {
     return 0;
@@ -114,4 +121,14 @@ function handleClick(i: string) {
   will-change: opacity, background-color;
   transition: opacity 0.3s, background-color 0.3s;
 }
+
+.no-data {
+  height: 100%;
+  color: #666;
+  box-sizing: border-box;
+  display: -webkit-box;
+  -webkit-box-orient: horizontal;
+  -webkit-box-pack: center;
+  -webkit-box-align: center;
+}
 </style>
diff --git a/src/views/dashboard/related/topology/components/Graph.vue 
b/src/views/dashboard/related/topology/components/Graph.vue
index d8d1279..aed1c8a 100644
--- a/src/views/dashboard/related/topology/components/Graph.vue
+++ b/src/views/dashboard/related/topology/components/Graph.vue
@@ -24,7 +24,7 @@ limitations under the License. -->
       <Settings @update="updateSettings" @updateNodes="freshNodes" />
     </div>
     <div class="tool">
-      <span v-show="config.graph.showDepth">
+      <span v-show="graphConfig.showDepth">
         <span class="label">{{ t("currentDepth") }}</span>
         <Selector
           class="inputs"
@@ -69,7 +69,14 @@ limitations under the License. -->
 </template>
 <script lang="ts" setup>
 import type { PropType } from "vue";
-import { ref, onMounted, onBeforeUnmount, reactive, watch } from "vue";
+import {
+  ref,
+  onMounted,
+  onBeforeUnmount,
+  reactive,
+  watch,
+  computed,
+} from "vue";
 import { useI18n } from "vue-i18n";
 import * as d3 from "d3";
 import d3tip from "d3-tip";
@@ -111,7 +118,7 @@ const loading = ref<boolean>(false);
 const simulation = ref<any>(null);
 const svg = ref<Nullable<any>>(null);
 const chart = ref<Nullable<HTMLDivElement>>(null);
-const tip = ref<any>(null);
+const tip = ref<Nullable<HTMLDivElement>>(null);
 const graph = ref<any>(null);
 const node = ref<any>(null);
 const link = ref<any>(null);
@@ -124,7 +131,8 @@ const operationsPos = reactive<{ x: number; y: number }>({ 
x: NaN, y: NaN });
 const items = ref<
   { id: string; title: string; func: any; dashboard?: string }[]
 >([]);
-const depth = ref<number>(props.config.graph.depth || 2);
+const graphConfig = computed(() => props.config.graph || {});
+const depth = ref<number>(graphConfig.value.depth || 2);
 
 onMounted(async () => {
   loading.value = true;
diff --git a/src/views/dashboard/related/topology/utils/legend.ts 
b/src/views/dashboard/related/topology/utils/legend.ts
index bb88673..929f879 100644
--- a/src/views/dashboard/related/topology/utils/legend.ts
+++ b/src/views/dashboard/related/topology/utils/legend.ts
@@ -27,15 +27,15 @@ export default function topoLegend(
       .append("image")
       .attr("width", 30)
       .attr("height", 30)
-      .attr("x", clientWidth - (item === "CUBEERROR" ? 200 : 410))
-      .attr("y", clientHeight + 50)
+      .attr("x", clientWidth - 140)
+      .attr("y", clientHeight + (item === "CUBEERROR" ? 50 : 0))
       .attr("xlink:href", () =>
         item === "CUBEERROR" ? icons.CUBEERROR : icons.CUBE
       );
     graph
       .append("text")
-      .attr("x", clientWidth - (item === "CUBEERROR" ? 170 : 380))
-      .attr("y", clientHeight + 70)
+      .attr("x", clientWidth - 110)
+      .attr("y", clientHeight + (item === "CUBEERROR" ? 70 : 20))
       .text(() => {
         const desc = description || {};
         return item === "CUBEERROR" ? desc.unhealthy || "" : desc.healthy || 
"";

Reply via email to