fix: polish pages and validate data (#46)

This commit is contained in:
Fine0830 2022-03-30 16:29:19 +08:00 committed by GitHub
parent 61d182b986
commit 767c92c60d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 241 additions and 192 deletions

View File

@ -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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <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> </template>
<script lang="ts" setup> <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 type { PropType } from "vue";
import { useECharts } from "@/hooks/useEcharts"; import { useECharts } from "@/hooks/useEcharts";
import { addResizeListener, removeResizeListener } from "@/utils/event"; import { addResizeListener, removeResizeListener } from "@/utils/event";
@ -31,11 +44,16 @@ const props = defineProps({
height: { type: String, default: "100%" }, height: { type: String, default: "100%" },
width: { type: String, default: "100%" }, width: { type: String, default: "100%" },
option: { option: {
type: Object as PropType<{ [key: string]: unknown }>, type: Object as PropType<{ [key: string]: any }>,
default: () => ({}), default: () => ({}),
}, },
}); });
const available = computed(
() =>
Array.isArray(props.option.series) &&
props.option.series[0] &&
props.option.series[0].data
);
onMounted(async () => { onMounted(async () => {
await setOptions(props.option); await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize); chartRef.value && addResizeListener(unref(chartRef), resize);
@ -65,3 +83,14 @@ onBeforeUnmount(() => {
removeResizeListener(unref(chartRef), resize); removeResizeListener(unref(chartRef), resize);
}); });
</script> </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>

View File

@ -26,6 +26,8 @@ export enum MetricQueryTypes {
export enum Calculations { export enum Calculations {
Percentage = "percentage", Percentage = "percentage",
ByteToKB = "byteToKB", ByteToKB = "byteToKB",
ByteToMB = "ByteToMB",
ByteToGB = "ByteToGB",
Apdex = "apdex", Apdex = "apdex",
Precision = "precision", Precision = "precision",
ConvertSeconds = "convertSeconds", ConvertSeconds = "convertSeconds",

View File

@ -28,6 +28,9 @@ export function useQueryProcessor(config: any) {
if (!(config.metrics && config.metrics[0])) { if (!(config.metrics && config.metrics[0])) {
return; return;
} }
if (!(config.metricTypes && config.metricTypes[0])) {
return;
}
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
@ -221,13 +224,19 @@ export function useQueryPodsMetrics(
config: { metrics: string[]; metricTypes: string[] }, config: { metrics: string[]; metricTypes: string[] },
scope: string scope: string
) { ) {
if (!(config.metrics && config.metrics[0])) {
return;
}
if (!(config.metricTypes && config.metricTypes[0])) {
return;
}
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const conditions: { [key: string]: unknown } = { const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime, duration: appStore.durationTime,
}; };
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
const { currentService } = selectorStore; const currentService = selectorStore.currentService || {};
const fragmentList = pods.map( const fragmentList = pods.map(
( (
d: (Instance | Endpoint | Service) & { normal: boolean }, d: (Instance | Endpoint | Service) & { normal: boolean },
@ -324,6 +333,12 @@ export function aggregation(val: number, config: any): number | string {
case Calculations.ByteToKB: case Calculations.ByteToKB:
data = val / 1024; data = val / 1024;
break; break;
case Calculations.ByteToMB:
data = val / 1024 / 1024;
break;
case Calculations.ByteToGB:
data = val / 1024 / 1024 / 1024;
break;
case Calculations.Apdex: case Calculations.Apdex:
data = val / 10000; data = val / 10000;
break; break;

View File

@ -38,8 +38,8 @@ const msg = {
health: "Health", health: "Health",
groupName: "Group Name", groupName: "Group Name",
topologies: "Topologies", topologies: "Topologies",
dataPanel: "Data Panel", dataPanel: "Data Plane",
controlPanel: "Control Panel", controlPanel: "Control Plane",
eventList: "Event List", eventList: "Event List",
databasePanel: "Database Panel", databasePanel: "Database Panel",
meshServicePanel: "Service Panel", meshServicePanel: "Service Panel",
@ -129,6 +129,7 @@ const msg = {
textAlign: "Text Align", textAlign: "Text Align",
metricLabel: "Metric Label", metricLabel: "Metric Label",
showUnit: "Show Unit", showUnit: "Show Unit",
noGraph: "No Graph",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
secondTip: "Select Second", secondTip: "Select Second",

View File

@ -129,6 +129,7 @@ const msg = {
textAlign: "文本对齐", textAlign: "文本对齐",
metricLabel: "指标标签", metricLabel: "指标标签",
showUnit: "显示单位", showUnit: "显示单位",
noGraph: "无图表",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
secondTip: "选择秒数", secondTip: "选择秒数",

View File

@ -21,14 +21,6 @@ export const NewControl = {
h: 12, h: 12,
i: "0", i: "0",
type: "Widget", type: "Widget",
widget: {
title: "",
},
graph: {},
standard: {},
metrics: [""],
metricTypes: [""],
metricConfig: [],
}; };
export const TextConfig = { export const TextConfig = {
fontColor: "white", fontColor: "white",

View File

@ -28,13 +28,12 @@ export interface LayoutConfig {
w: number; w: number;
h: number; h: number;
i: string; i: string;
widget: WidgetConfig; widget?: WidgetConfig;
graph: GraphConfig; graph?: GraphConfig;
standard: StandardConfig; metrics?: string[];
metrics: string[];
type: string; type: string;
metricTypes: string[]; metricTypes?: string[];
children?: any; children?: { name: string; children: LayoutConfig[] }[];
activedTabIndex?: number; activedTabIndex?: number;
metricConfig?: MetricConfigOpt[]; metricConfig?: MetricConfigOpt[];
} }
@ -53,20 +52,6 @@ export interface WidgetConfig {
tips?: string; 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 = export type GraphConfig =
| BarConfig | BarConfig
| LineConfig | LineConfig

View File

@ -91,12 +91,13 @@ import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid; const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const url = ref(originConfig.graph.url || ""); const url = ref(originConfig.graph.url || "");
const backgroundColor = ref(originConfig.graph.backgroundColor || "green"); const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(originConfig.graph.fontColor || "white"); const fontColor = ref(graph.fontColor || "white");
const content = ref<string>(originConfig.graph.content || ""); const content = ref<string>(graph.content || "");
const fontSize = ref<number>(originConfig.graph.fontSize || 12); const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(originConfig.graph.textAlign || "left"); const textAlign = ref(graph.textAlign || "left");
const Colors = [ const Colors = [
{ {
label: "Green", label: "Green",

View File

@ -48,9 +48,9 @@ import { Option } from "@/types/app";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore; const graph = dashboardStore.selectedGrid.graph || {};
const showDepth = ref<boolean>(selectedGrid.graph.showDepth); const showDepth = ref<boolean>(graph.showDepth);
const depth = ref<number>(selectedGrid.graph.depth || 2); const depth = ref<number>(graph.depth || 2);
function applyConfig() { function applyConfig() {
dashboardStore.setConfigs(dashboardStore.selectedGrid); dashboardStore.setConfigs(dashboardStore.selectedGrid);

View File

@ -16,20 +16,20 @@ limitations under the License. -->
<div class="widget-config flex-v"> <div class="widget-config flex-v">
<div class="graph" v-loading="loading"> <div class="graph" v-loading="loading">
<div class="header"> <div class="header">
<span>{{ dashboardStore.selectedGrid.widget.title || "" }}</span> <span>{{ widget.title || "" }}</span>
<div class="tips" v-show="dashboardStore.selectedGrid.widget.tips"> <div class="tips" v-show="widget.tips">
<el-tooltip :content="dashboardStore.selectedGrid.widget.tips || ''"> <el-tooltip :content="widget.tips || ''">
<Icon iconName="info_outline" size="sm" /> <Icon iconName="info_outline" size="sm" />
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
<div class="render-chart"> <div class="render-chart">
<component <component
:is="dashboardStore.selectedGrid.graph.type" :is="graph.type"
:intervalTime="appStoreWithOut.intervalTime" :intervalTime="appStoreWithOut.intervalTime"
:data="states.source" :data="states.source"
:config="{ :config="{
...dashboardStore.selectedGrid.graph, ...graph,
i: dashboardStore.selectedGrid.i, i: dashboardStore.selectedGrid.i,
metrics: dashboardStore.selectedGrid.metrics, metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes, metricTypes: dashboardStore.selectedGrid.metricTypes,
@ -38,7 +38,7 @@ limitations under the License. -->
:isEdit="isEdit" :isEdit="isEdit"
@changeOpt="setStatus" @changeOpt="setStatus"
/> />
<div v-show="!dashboardStore.selectedGrid.graph.type" class="no-data"> <div v-show="!graph.type" class="no-data">
{{ t("noData") }} {{ t("noData") }}
</div> </div>
</div> </div>
@ -56,7 +56,7 @@ limitations under the License. -->
/> />
</el-collapse-item> </el-collapse-item>
<el-collapse-item :title="t('graphStyles')" name="2"> <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>
<el-collapse-item :title="t('widgetOptions')" name="3"> <el-collapse-item :title="t('widgetOptions')" name="3">
<WidgetOptions /> <WidgetOptions />
@ -74,7 +74,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { reactive, defineComponent, ref } from "vue"; import { reactive, defineComponent, ref, computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
@ -111,6 +111,8 @@ export default defineComponent({
visType: [], visType: [],
}); });
const originConfig = dashboardStore.selectedGrid; const originConfig = dashboardStore.selectedGrid;
const widget = computed(() => dashboardStore.selectedGrid.widget || {});
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
function getSource(source: unknown) { function getSource(source: unknown) {
states.source = source; states.source = source;
@ -148,6 +150,8 @@ export default defineComponent({
setLoading, setLoading,
setStatus, setStatus,
isEdit, isEdit,
widget,
graph,
}; };
}, },
}); });

View File

@ -42,12 +42,13 @@ import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore; const { selectedGrid } = dashboardStore;
const title = ref<string>(selectedGrid.widget.title || ""); const widget = dashboardStore.selectedGrid.widget || {};
const tips = ref<string>(selectedGrid.widget.tips || ""); const title = ref<string>(widget.title || "");
const tips = ref<string>(widget.tips || "");
function updateWidgetConfig(param: { [key: string]: unknown }) { function updateWidgetConfig(param: { [key: string]: unknown }) {
const widget = { const widget = {
...selectedGrid.widget, ...dashboardStore.selectedGrid.widget,
...param, ...param,
}; };
dashboardStore.selectWidget({ ...selectedGrid, widget }); dashboardStore.selectWidget({ ...selectedGrid, widget });

View File

@ -34,7 +34,8 @@ import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); 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 }) { function updateConfig(param: { [key: string]: unknown }) {
const graph = { const graph = {

View File

@ -43,9 +43,7 @@ limitations under the License. -->
:value="states.metricTypes[index]" :value="states.metricTypes[index]"
:options="states.metricTypeList[index]" :options="states.metricTypeList[index]"
size="small" size="small"
:disabled=" :disabled="graph.type && !states.isList && index !== 0"
dashboardStore.selectedGrid.graph.type && !states.isList && index !== 0
"
@change="changeMetricType(index, $event)" @change="changeMetricType(index, $event)"
class="selectors" class="selectors"
/> />
@ -85,11 +83,11 @@ limitations under the License. -->
<div>{{ t("visualization") }}</div> <div>{{ t("visualization") }}</div>
<div class="chart-types"> <div class="chart-types">
<span <span
v-for="(type, index) in states.visTypes" v-for="(type, index) in setVisTypes"
:key="index" :key="index"
@click="changeChartType(type)" @click="changeChartType(type)"
:class="{ :class="{
active: type.value === dashboardStore.selectedGrid.graph.type, active: type.value === graph.type,
}" }"
> >
{{ type.label }} {{ type.label }}
@ -97,7 +95,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from "vue"; import { reactive, ref, computed } from "vue";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { import {
@ -124,24 +122,26 @@ import Standard from "./Standard.vue";
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits(["update", "loading", "changeOpt"]); const emit = defineEmits(["update", "loading", "changeOpt"]);
const dashboardStore = useDashboardStore(); 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<{ const states = reactive<{
metrics: string[]; metrics: string[];
metricTypes: string[]; metricTypes: string[];
metricTypeList: Option[][]; metricTypeList: Option[][];
visTypes: Option[];
isList: boolean; isList: boolean;
metricList: (Option & { type: string })[]; metricList: (Option & { type: string })[];
dashboardName: string; dashboardName: string;
dashboardList: ((DashboardItem & { label: string; value: string }) | any)[]; dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
}>({ }>({
metrics: metrics && metrics.length ? metrics : [""], metrics: metrics.value.length ? metrics.value : [""],
metricTypes: metricTypes && metricTypes.length ? metricTypes : [""], metricTypes: metricTypes.value.length ? metricTypes.value : [""],
metricTypeList: [], metricTypeList: [],
visTypes: [],
isList: false, isList: false,
metricList: [], metricList: [],
dashboardName: graph.dashboardName, dashboardName: graph.value.dashboardName,
dashboardList: [{ label: "", value: "" }], dashboardList: [{ label: "", value: "" }],
}); });
const currentMetricConfig = ref<MetricConfigOpt>({ const currentMetricConfig = ref<MetricConfigOpt>({
@ -152,15 +152,33 @@ const currentMetricConfig = ref<MetricConfigOpt>({
sortOrder: "DES", sortOrder: "DES",
}); });
states.isList = ListChartTypes.includes(graph.type); states.isList = ListChartTypes.includes(graph.value.type);
const defaultLen = ref<number>(states.isList ? 5 : 20); const defaultLen = ref<number>(states.isList ? 5 : 20);
states.visTypes = setVisTypes();
setDashboards(); setDashboards();
setMetricType(); 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) { async function setMetricType(chart?: any) {
const graph = chart || dashboardStore.selectedGrid.graph; const g = chart || dashboardStore.selectedGrid.graph || {};
const json = await dashboardStore.fetchMetricList(); const json = await dashboardStore.fetchMetricList();
if (json.errors) { if (json.errors) {
ElMessage.error(json.errors); ElMessage.error(json.errors);
@ -172,7 +190,7 @@ async function setMetricType(chart?: any) {
if (d.type === MetricsType.REGULAR_VALUE) { if (d.type === MetricsType.REGULAR_VALUE) {
return d; return d;
} }
} else if (graph.type === "Table") { } else if (g.type === "Table") {
if ( if (
d.type === MetricsType.LABELED_VALUE || d.type === MetricsType.LABELED_VALUE ||
d.type === MetricsType.REGULAR_VALUE d.type === MetricsType.REGULAR_VALUE
@ -203,7 +221,7 @@ async function setMetricType(chart?: any) {
...dashboardStore.selectedGrid, ...dashboardStore.selectedGrid,
metrics: states.metrics, metrics: states.metrics,
metricTypes: states.metricTypes, metricTypes: states.metricTypes,
graph, graph: g,
}); });
states.metricTypeList = []; states.metricTypeList = [];
for (const metric of metrics) { for (const metric of metrics) {
@ -220,7 +238,7 @@ async function setMetricType(chart?: any) {
} }
function setDashboards(type?: string) { function setDashboards(type?: string) {
const graph = type || dashboardStore.selectedGrid.graph; const chart = type || dashboardStore.selectedGrid.graph || {};
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.reduce( const arr = list.reduce(
( (
@ -229,9 +247,9 @@ function setDashboards(type?: string) {
) => { ) => {
if (d.layer === dashboardStore.layerId) { if (d.layer === dashboardStore.layerId) {
if ( if (
(d.entity === EntityType[0].value && graph.type === "ServiceList") || (d.entity === EntityType[0].value && chart.type === "ServiceList") ||
(d.entity === EntityType[2].value && graph.type === "EndpointList") || (d.entity === EntityType[2].value && chart.type === "EndpointList") ||
(d.entity === EntityType[3].value && graph.type === "InstanceList") (d.entity === EntityType[3].value && chart.type === "InstanceList")
) { ) {
prev.push({ prev.push({
...d, ...d,
@ -248,26 +266,9 @@ function setDashboards(type?: string) {
states.dashboardList = arr.length ? arr : [{ label: "", value: "" }]; 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) { function changeChartType(item: Option) {
const graph = DefaultGraphConfig[item.value]; const chart = DefaultGraphConfig[item.value] || {};
states.isList = ListChartTypes.includes(graph.type); states.isList = ListChartTypes.includes(chart.type);
if (states.isList) { if (states.isList) {
dashboardStore.selectWidget({ dashboardStore.selectWidget({
...dashboardStore.selectedGrid, ...dashboardStore.selectedGrid,
@ -278,8 +279,8 @@ function changeChartType(item: Option) {
states.metricTypes = [""]; states.metricTypes = [""];
defaultLen.value = 5; defaultLen.value = 5;
} }
setMetricType(graph); setMetricType(chart);
setDashboards(graph.type); setDashboards(chart.type);
states.dashboardName = ""; states.dashboardName = "";
defaultLen.value = 10; defaultLen.value = 10;
} }
@ -415,7 +416,7 @@ function setMetricTypeList(type: string) {
if (type !== MetricsType.REGULAR_VALUE) { if (type !== MetricsType.REGULAR_VALUE) {
return MetricTypes[type]; return MetricTypes[type];
} }
if (states.isList || dashboardStore.selectedGrid.graph.type === "Table") { if (states.isList || graph.value.type === "Table") {
return [ return [
{ label: "read all values in the duration", value: "readMetricsValues" }, { label: "read all values in the duration", value: "readMetricsValues" },
{ {

View File

@ -56,7 +56,7 @@ limitations under the License. -->
:clearable="true" :clearable="true"
/> />
</div> </div>
<div class="item" v-show="isTopn"> <div class="item mb-10" v-show="isTopn">
<span class="label">{{ t("sortOrder") }}</span> <span class="label">{{ t("sortOrder") }}</span>
<SelectSingle <SelectSingle
:value="currentMetric.sortOrder || 'DES'" :value="currentMetric.sortOrder || 'DES'"
@ -67,11 +67,12 @@ limitations under the License. -->
</div> </div>
<div class="item" v-show="isTopn"> <div class="item" v-show="isTopn">
<span class="label">{{ t("maxItemNum") }}</span> <span class="label">{{ t("maxItemNum") }}</span>
<el-input-number <el-input
class="selectors" class="selectors"
v-model="currentMetric.topN" v-model="currentMetric.topN"
size="small" size="small"
placeholder="none" placeholder="none"
type="number"
:min="1" :min="1"
:max="100" :max="100"
@change="changeConfigs(index, { topN: currentMetric.topN || 10 })" @change="changeConfigs(index, { topN: currentMetric.topN || 10 })"
@ -102,13 +103,10 @@ const currentMetric = ref<MetricConfigOpt>({
...props.currentMetricConfig, ...props.currentMetricConfig,
topN: props.currentMetricConfig.topN || 10, topN: props.currentMetricConfig.topN || 10,
}); });
const metricType = ref<string>( const metricTypes = dashboardStore.selectedGrid.metricTypes || [];
dashboardStore.selectedGrid.metricTypes[props.index] const metricType = ref<string>(metricTypes[props.index]);
);
const isTopn = computed(() => const isTopn = computed(() =>
["sortMetrics", "readSampledRecords"].includes( ["sortMetrics", "readSampledRecords"].includes(metricTypes[props.index])
dashboardStore.selectedGrid.metricTypes[props.index]
)
); );
function changeConfigs( function changeConfigs(
index: number, index: number,

View File

@ -17,17 +17,17 @@ limitations under the License. -->
<div class="header flex-h"> <div class="header flex-h">
<div> <div>
<span> <span>
{{ data.widget?.title || "" }} {{ widget.title || "" }}
</span> </span>
</div> </div>
<div> <div>
<el-tooltip :content="data.widget?.tips"> <el-tooltip :content="widget.tips || ''">
<span> <span>
<Icon <Icon
iconName="info_outline" iconName="info_outline"
size="sm" size="sm"
class="operation" class="operation"
v-show="data.widget?.tips" v-show="widget.tips"
/> />
</span> </span>
</el-tooltip> </el-tooltip>
@ -51,26 +51,26 @@ limitations under the License. -->
</el-popover> </el-popover>
</div> </div>
</div> </div>
<div class="body" v-if="data.graph?.type" v-loading="loading"> <div class="body" v-if="graph.type" v-loading="loading">
<component <component
:is="data.graph.type" :is="graph.type"
:intervalTime="appStore.intervalTime" :intervalTime="appStore.intervalTime"
:data="state.source" :data="state.source"
:config="{ :config="{
...data.graph, ...data.graph,
metrics: data.metrics, metrics: data.metrics || [''],
metricTypes: data.metricTypes, metricTypes: data.metricTypes || [''],
i: data.i, i: data.i,
metricConfig: data.metricConfig, metricConfig: data.metricConfig,
}" }"
:needQuery="needQuery" :needQuery="needQuery"
/> />
</div> </div>
<div v-else class="no-data">{{ t("noData") }}</div> <div v-else class="no-data">{{ t("noGraph") }}</div>
</div> </div>
</template> </template>
<script lang="ts"> <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 type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard"; import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
@ -88,7 +88,7 @@ import { EntityType, ListChartTypes } from "../data";
const props = { const props = {
data: { data: {
type: Object as PropType<LayoutConfig>, type: Object as PropType<LayoutConfig>,
default: () => ({ widget: {} }), default: () => ({ widget: {}, graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: false }, needQuery: { type: Boolean, default: false },
@ -107,14 +107,19 @@ export default defineComponent({
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); 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) { if ((props.needQuery || !dashboardStore.currentDashboard.id) && !isList) {
queryMetrics(); queryMetrics();
} }
async function 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 catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
const params = await useQueryProcessor({ ...props.data, catalog }); const params = await useQueryProcessor({ ...props.data, catalog });
@ -129,8 +134,8 @@ export default defineComponent({
return; return;
} }
const d = { const d = {
metrics: props.data.metrics, metrics: props.data.metrics || [],
metricTypes: props.data.metricTypes, metricTypes: props.data.metricTypes || [],
metricConfig: props.data.metricConfig || [], metricConfig: props.data.metricConfig || [],
}; };
state.source = useSourceProcessor(json, d); state.source = useSourceProcessor(json, d);
@ -149,7 +154,7 @@ export default defineComponent({
} }
} }
watch( watch(
() => [props.data.metricTypes, props.data.metrics, props.data.standard], () => [props.data.metricTypes, props.data.metrics],
() => { () => {
if (!dashboardStore.selectedGrid) { if (!dashboardStore.selectedGrid) {
return; return;
@ -157,11 +162,11 @@ export default defineComponent({
if (props.data.i !== dashboardStore.selectedGrid.i) { if (props.data.i !== dashboardStore.selectedGrid.i) {
return; return;
} }
const isList = ListChartTypes.includes(props.data.graph.type || ""); const isList = ListChartTypes.includes(
if ( (props.data.graph && props.data.graph.type) || ""
ListChartTypes.includes(dashboardStore.selectedGrid.graph.type) || );
isList const chart = dashboardStore.selectedGrid.graph || {};
) { if (ListChartTypes.includes(chart.type) || isList) {
return; return;
} }
queryMetrics(); queryMetrics();
@ -170,7 +175,9 @@ export default defineComponent({
watch( watch(
() => [selectorStore.currentService, selectorStore.currentDestService], () => [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) { if (isList) {
return; return;
} }
@ -209,6 +216,8 @@ export default defineComponent({
loading, loading,
dashboardStore, dashboardStore,
t, t,
graph,
widget,
}; };
}, },
}); });

View File

@ -269,8 +269,10 @@ export const TextColors: { [key: string]: string } = {
export const CalculationOpts = [ export const CalculationOpts = [
{ label: "Percentage", value: "percentage" }, { label: "Percentage", value: "percentage" },
{ label: "Byte to KB", value: "byteToKB" },
{ label: "Apdex", value: "apdex" }, { 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", label: "Convert milliseconds to YYYY-MM-DD HH:mm:ss",
value: "convertMilliseconds", value: "convertMilliseconds",

View File

@ -137,13 +137,15 @@ function getOption() {
color, color,
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
// backgroundColor: "rgb(50,50,50)", zlevel: 1000,
// textStyle: { z: 60,
// fontSize: 13, backgroundColor: "rgb(50,50,50)",
// color: "#ccc", textStyle: {
// }, fontSize: 13,
// enterable: true, color: "#ccc",
// extraCssText: "max-height: 300px; overflow: auto;", },
enterable: true,
extraCssText: "max-height: 300px; overflow: auto; border: none",
}, },
legend: { legend: {
type: "scroll", type: "scroll",

View File

@ -15,21 +15,17 @@ limitations under the License. -->
<template> <template>
<div <div
v-if="!isNaN(singleVal)"
class="chart-card" class="chart-card"
:class="{ center: config.textAlign === 'center' }" :class="{ center: config.textAlign === 'center' }"
:style="{ fontSize: `${config.fontSize}px`, textAlign: config.textAlign }" :style="{ fontSize: `${config.fontSize}px`, textAlign: config.textAlign }"
> >
{{ {{ singleVal.toFixed(2) }}
typeof singleVal === "string"
? singleVal
: isNaN(singleVal)
? null
: singleVal.toFixed(2)
}}
<span v-show="config.showUnit"> <span v-show="config.showUnit">
{{ metricConfig[0]?.unit }} {{ metricConfig[0]?.unit }}
</span> </span>
</div> </div>
<div class="center no-data" v-else>No Data</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, PropType } from "vue"; import { computed, PropType } from "vue";
@ -53,7 +49,7 @@ const props = defineProps({
}); });
const metricConfig = computed(() => props.config.metricConfig || []); const metricConfig = computed(() => props.config.metricConfig || []);
const key = computed(() => Object.keys(props.data)[0]); const key = computed(() => Object.keys(props.data)[0]);
const singleVal = computed(() => props.data[key.value]); const singleVal = computed(() => Number(props.data[key.value]));
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chart-card { .chart-card {
@ -68,4 +64,9 @@ const singleVal = computed(() => props.data[key.value]);
-webkit-box-pack: center; -webkit-box-pack: center;
-webkit-box-align: center; -webkit-box-align: center;
} }
.no-data {
height: 100%;
color: #666;
}
</style> </style>

View File

@ -65,17 +65,6 @@ limitations under the License. -->
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </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> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -107,7 +96,13 @@ const props = defineProps({
metricTypes: string[]; metricTypes: string[];
} & { metricConfig: MetricConfigOpt[] } } & { metricConfig: MetricConfigOpt[] }
>, >,
default: () => ({ dashboardName: "", fontSize: 12, i: "" }), default: () => ({
metrics: [],
metricTypes: [],
dashboardName: "",
fontSize: 12,
i: "",
}),
}, },
intervalTime: { type: Array as PropType<string[]>, default: () => [] }, intervalTime: { type: Array as PropType<string[]>, default: () => [] },
isEdit: { type: Boolean, default: false }, isEdit: { type: Boolean, default: false },
@ -117,7 +112,6 @@ const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false); const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]); const endpoints = ref<Endpoint[]>([]);
const pageSize = 10;
const searchText = ref<string>(""); const searchText = ref<string>("");
queryEndpoints(); queryEndpoints();
@ -133,18 +127,16 @@ async function queryEndpoints() {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
return; return;
} }
endpoints.value = selectorStore.pods.filter( endpoints.value = selectorStore.pods;
(d: unknown, index: number) => index < pageSize
);
queryEndpointMetrics(endpoints.value); queryEndpointMetrics(endpoints.value);
} }
async function queryEndpointMetrics(currentPods: Endpoint[]) { async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (!currentPods.length) { if (!currentPods.length) {
return; return;
} }
const metrics = props.config.metrics.filter((d: string) => d); const metrics = (props.config.metrics || []).filter((d: string) => d);
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0]) { if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics( const params = await useQueryPodsMetrics(
currentPods, currentPods,
props.config, props.config,
@ -180,14 +172,6 @@ function clickEndpoint(scope: any) {
`/dashboard/${d.layer}/${d.entity}/${selectorStore.currentService.id}/${scope.row.id}/${d.name}` `/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() { async function searchList() {
await queryEndpoints(); await queryEndpoints();
} }

View File

@ -18,7 +18,6 @@ limitations under the License. -->
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { StandardConfig } from "@/types/dashboard";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
@ -31,11 +30,7 @@ const props = defineProps({
intervalTime: { type: Array as PropType<string[]>, default: () => [] }, intervalTime: { type: Array as PropType<string[]>, default: () => [] },
config: { config: {
type: Object as PropType<any>, type: Object as PropType<any>,
default: () => ({}), default: () => ({ metrics: [] }),
},
standard: {
type: Object as PropType<StandardConfig>,
default: () => ({}),
}, },
}); });
const option = computed(() => getOption()); const option = computed(() => getOption());

View File

@ -166,9 +166,9 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
if (!currentInstances.length) { if (!currentInstances.length) {
return; 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( const params = await useQueryPodsMetrics(
currentInstances, currentInstances,
props.config, props.config,

View File

@ -153,7 +153,7 @@ function getOption() {
color: "#ccc", color: "#ccc",
}, },
enterable: true, enterable: true,
extraCssText: "max-height: 300px; overflow: auto;", extraCssText: "max-height: 300px; overflow: auto; border: none",
}, },
legend: { legend: {
type: "scroll", type: "scroll",

View File

@ -210,9 +210,9 @@ async function queryServiceMetrics(currentServices: Service[]) {
if (!currentServices.length) { if (!currentServices.length) {
return; 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( const params = await useQueryPodsMetrics(
currentServices, currentServices,
props.config, props.config,

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <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="chart-slow-i" v-for="(i, index) in data[key]" :key="index">
<div class="ell tools flex-h"> <div class="ell tools flex-h">
<div> <div>
@ -35,11 +35,12 @@ limitations under the License. -->
<el-progress <el-progress
:stroke-width="6" :stroke-width="6"
:percentage="(i.value / maxValue) * 100" :percentage="(i.value / maxValue) * 100"
:color="TextColors[config.color]" :color="TextColors[config.color || 'purple']"
:show-text="false" :show-text="false"
/> />
</div> </div>
</div> </div>
<div class="center no-data" v-else>No Data</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from "vue"; import type { PropType } from "vue";
@ -61,6 +62,12 @@ const props = defineProps({
intervalTime: { type: Array as PropType<string[]>, default: () => [] }, intervalTime: { type: Array as PropType<string[]>, default: () => [] },
}); });
const key = computed(() => Object.keys(props.data)[0] || ""); 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(() => { const maxValue = computed(() => {
if (!(props.data[key.value] && props.data[key.value].length)) { if (!(props.data[key.value] && props.data[key.value].length)) {
return 0; return 0;
@ -114,4 +121,14 @@ function handleClick(i: string) {
will-change: opacity, background-color; will-change: opacity, background-color;
transition: opacity 0.3s, background-color 0.3s; 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> </style>

View File

@ -24,7 +24,7 @@ limitations under the License. -->
<Settings @update="updateSettings" @updateNodes="freshNodes" /> <Settings @update="updateSettings" @updateNodes="freshNodes" />
</div> </div>
<div class="tool"> <div class="tool">
<span v-show="config.graph.showDepth"> <span v-show="graphConfig.showDepth">
<span class="label">{{ t("currentDepth") }}</span> <span class="label">{{ t("currentDepth") }}</span>
<Selector <Selector
class="inputs" class="inputs"
@ -69,7 +69,14 @@ limitations under the License. -->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from "vue"; 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 { useI18n } from "vue-i18n";
import * as d3 from "d3"; import * as d3 from "d3";
import d3tip from "d3-tip"; import d3tip from "d3-tip";
@ -111,7 +118,7 @@ const loading = ref<boolean>(false);
const simulation = ref<any>(null); const simulation = ref<any>(null);
const svg = ref<Nullable<any>>(null); const svg = ref<Nullable<any>>(null);
const chart = ref<Nullable<HTMLDivElement>>(null); const chart = ref<Nullable<HTMLDivElement>>(null);
const tip = ref<any>(null); const tip = ref<Nullable<HTMLDivElement>>(null);
const graph = ref<any>(null); const graph = ref<any>(null);
const node = ref<any>(null); const node = ref<any>(null);
const link = 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< const items = ref<
{ id: string; title: string; func: any; dashboard?: string }[] { 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 () => { onMounted(async () => {
loading.value = true; loading.value = true;

View File

@ -27,15 +27,15 @@ export default function topoLegend(
.append("image") .append("image")
.attr("width", 30) .attr("width", 30)
.attr("height", 30) .attr("height", 30)
.attr("x", clientWidth - (item === "CUBEERROR" ? 200 : 410)) .attr("x", clientWidth - 140)
.attr("y", clientHeight + 50) .attr("y", clientHeight + (item === "CUBEERROR" ? 50 : 0))
.attr("xlink:href", () => .attr("xlink:href", () =>
item === "CUBEERROR" ? icons.CUBEERROR : icons.CUBE item === "CUBEERROR" ? icons.CUBEERROR : icons.CUBE
); );
graph graph
.append("text") .append("text")
.attr("x", clientWidth - (item === "CUBEERROR" ? 170 : 380)) .attr("x", clientWidth - 110)
.attr("y", clientHeight + 70) .attr("y", clientHeight + (item === "CUBEERROR" ? 70 : 20))
.text(() => { .text(() => {
const desc = description || {}; const desc = description || {};
return item === "CUBEERROR" ? desc.unhealthy || "" : desc.healthy || ""; return item === "CUBEERROR" ? desc.unhealthy || "" : desc.healthy || "";