feat: add graph, widget config

This commit is contained in:
Qiuxia Fan 2022-01-05 16:20:25 +08:00
parent 6ce7265d3e
commit eab32b305a
14 changed files with 432 additions and 219 deletions

View File

@ -53,8 +53,10 @@ const msg = {
instance: "Instance", instance: "Instance",
create: "Create", create: "Create",
loading: "Loading", loading: "Loading",
graphMetric: "Graph your metrics", selectVisualization: "Select your visualization",
selectVisualization: "Select you visualization", graphStyles: "Graph styles",
widgetOptions: "Widget options",
standardOptions: "Standard options",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
secondTip: "Select Second", secondTip: "Select Second",
@ -221,7 +223,7 @@ const msg = {
grade: "Grade", grade: "Grade",
relatedTraceLogs: "Related Logs", relatedTraceLogs: "Related Logs",
setConditions: "More Conditions", setConditions: "More Conditions",
metricName: "Metric Names", metricName: "Select Metric Names",
keywordsOfContent: "Keys Of Content", keywordsOfContent: "Keys Of Content",
excludingKeywordsOfContent: "Exclude Keys Of Content", excludingKeywordsOfContent: "Exclude Keys Of Content",
return: "Return", return: "Return",

View File

@ -51,8 +51,10 @@ const msg = {
endpoint: "端点", endpoint: "端点",
create: "新建", create: "新建",
loading: "加载中", loading: "加载中",
graphMetric: "用图表示你的指标",
selectVisualization: "选择你的可视化", selectVisualization: "选择你的可视化",
graphStyles: "图形样式",
widgetOptions: "组件选项",
standardOptions: "标准选项",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
secondTip: "选择秒数", secondTip: "选择秒数",
@ -220,7 +222,7 @@ const msg = {
grade: "等级", grade: "等级",
relatedTraceLogs: "相关的日志", relatedTraceLogs: "相关的日志",
setConditions: "更多条件", setConditions: "更多条件",
metricName: "指标名称", metricName: "选择指标名称",
keywordsOfContent: "内容关键词", keywordsOfContent: "内容关键词",
excludingKeywordsOfContent: "内容不包含的关键词", excludingKeywordsOfContent: "内容不包含的关键词",
return: "返回", return: "返回",

View File

@ -18,6 +18,7 @@ limitations under the License. -->
<grid-layout /> <grid-layout />
<el-dialog <el-dialog
v-model="dashboardStore.showConfig" v-model="dashboardStore.showConfig"
title="Edit"
fullscreen fullscreen
@closed="dashboardStore.setConfigPanel(false)" @closed="dashboardStore.setConfigPanel(false)"
> >
@ -30,7 +31,7 @@ import { ElDialog } from "element-plus";
import GridLayout from "./panel/Layout.vue"; import GridLayout from "./panel/Layout.vue";
import { LayoutConfig } from "@/types/dashboard"; import { LayoutConfig } from "@/types/dashboard";
import Tool from "./panel/Tool.vue"; import Tool from "./panel/Tool.vue";
import WidgetConfig from "./configuration/WidgetConfig.vue"; import WidgetConfig from "./configuration/ConfigEdit.vue";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -0,0 +1,242 @@
<!-- 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="widget-config flex-v">
<div class="graph" style="height: 350px; width: 100%">
<component
:is="states.chartType"
:intervalTime="appStoreWithOut.intervalTime"
:data="source"
/>
<span v-show="!source">{{ t("noData") }}</span>
</div>
<el-collapse
class="collapse"
v-model="states.activeNames"
:style="{ '--el-collapse-header-font-size': '15px' }"
>
<el-collapse-item :title="t('metricName')" name="1">
<div>
<Selector
:value="states.metrics"
:options="metricOpts"
:multiple="true"
size="mini"
placeholder="Select a metric"
@change="changeMetrics"
class="selectors"
/>
<Selector
v-show="states.valueType"
:value="states.valueType"
:options="states.valueTypes"
size="mini"
placeholder="Select a metric"
@change="changeValueType"
class="selectors"
/>
</div>
</el-collapse-item>
<el-collapse-item :title="t('selectVisualization')" name="2">
<div class="chart-types">
<span
v-for="(type, index) in ChartTypes"
:key="index"
@click="changeChartType(type)"
:class="{ active: type.value === states.chartType }"
>
{{ type.label }}
</span>
</div>
</el-collapse-item>
<el-collapse-item :title="t('graphStyles')" name="3">
<component :is="`${states.chartType}Config`" />
</el-collapse-item>
<el-collapse-item :title="t('widgetOptions')" name="4">
<WidgetConfig />
</el-collapse-item>
<el-collapse-item :title="t('standardOptions')" name="5">
Standard options
</el-collapse-item>
</el-collapse>
<div class="footer">
<el-button size="mini">
{{ t("cancel") }}
</el-button>
<el-button size="mini" type="primary">
{{ t("apply") }}
</el-button>
</div>
</div>
</template>
<script lang="ts">
import { reactive, defineComponent } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage, ElButton, ElCollapse, ElCollapseItem } from "element-plus";
import { ValuesTypes, MetricQueryTypes, ChartTypes } from "../data";
import { Option } from "@/types/app";
import Loading from "@/utils/loading";
import graphs from "../graphs";
import controls from "../controls";
import configs from "./graph-styles";
import WidgetConfig from "./WidgetConfig.vue";
export default defineComponent({
name: "ConfigEdit",
components: {
...graphs,
...controls,
...configs,
WidgetConfig,
ElButton,
ElCollapse,
ElCollapseItem,
},
setup() {
const states = reactive<{
metrics: string;
valueTypes: Option[];
valueType: string;
metricQueryType: string;
chartType: string;
activeNames: string;
}>({
metrics: "",
valueTypes: [],
valueType: "",
metricQueryType: "",
chartType: "Bar",
activeNames: "1",
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const appStoreWithOut = useAppStoreWithOut();
const { loading } = Loading();
async function changeMetrics(val: Option[]) {
if (!val.length) {
states.valueTypes = [];
states.valueType = "";
return;
}
const loadingInstance = loading({ text: t("loading"), fullscreen: true });
const resp = await dashboardStore.fetchMetricType(val[0].value);
loadingInstance.close();
if (resp.error) {
ElMessage.error(resp.data.error);
return;
}
const { typeOfMetrics } = resp.data;
states.valueTypes = ValuesTypes[typeOfMetrics];
states.valueType = ValuesTypes[typeOfMetrics][0].value;
}
function changeValueType(val: Option[]) {
states.valueType = String(val[0].value);
states.metricQueryType = (MetricQueryTypes as any)[states.valueType];
}
function changeChartType(item: Option) {
states.chartType = String(item.value);
}
const metricOpts = [
{ value: "service_apdex", label: "service_apdex" },
{ value: "service_sla", label: "service_sla" },
{ value: "service_cpm", label: "service_cpm" },
{ value: "service_resp_time", label: "service_resp_time" },
{ value: "service_percentile", label: "service_percentile" },
{
value: "service_mq_consume_latency",
label: "service_mq_consume_latency",
},
{ value: "service_mq_consume_count", label: "service_mq_consume_count" },
];
const source = {
count: [1, 2, 5, 4, 5, 6, 7, 3, 4, 5, 2, 1, 6, 9],
avg: [3, 2, 4, 4, 5, 6, 5, 3, 4, 1, 2, 1, 6, 10],
};
return {
states,
changeChartType,
changeValueType,
changeMetrics,
t,
appStoreWithOut,
ChartTypes,
source,
metricOpts,
};
},
});
</script>
<style lang="scss" scoped>
.widget-config {
position: relative;
width: 100%;
height: 100%;
}
.graph {
min-width: 1280px;
border: 1px solid #eee;
padding: 10px;
}
.selectors {
width: 500px;
margin-right: 10px;
}
.el-collapse-item__header {
font-weight: bold;
}
.config {
min-width: 1280px;
}
.chart-types {
span {
display: inline-block;
padding: 5px 10px;
border: 1px solid #ccc;
background-color: #fff;
border-right: 0;
cursor: pointer;
}
span:nth-last-child(1) {
border-right: 1px solid #ccc;
}
}
span.active {
background-color: #eee;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.collapse {
margin: 10px 0;
}
</style>

View File

@ -13,221 +13,44 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="widget-config"> <div class="item">
<div class="graph" style="height: 350px; width: 100%"> <span class="label">Title</span>
<component <el-input
:is="states.chartType" class="input"
:intervalTime="appStoreWithOut.intervalTime" v-model="title"
:data="source"
/>
<span v-show="!source">{{ t("noData") }}</span>
</div>
<div class="config">
<div class="metrics item">
<label>{{ t("graphMetric") }}</label>
<div class="name">{{ t("metricName") }}</div>
<Selector
:value="states.metrics"
:options="metricOpts"
:multiple="true"
size="mini" size="mini"
placeholder="Select a metric" placeholder="Please input title"
@change="changeMetrics"
class="selectors"
/> />
<Selector
v-show="states.valueType"
:value="states.valueType"
:options="states.valueTypes"
size="mini"
placeholder="Select a metric"
@change="changeValueType"
class="selectors"
/>
</div>
<div class="visualization item">
<label>{{ t("selectVisualization") }}</label>
<div class="chart-types">
<span
v-for="(type, index) in ChartTypes"
:key="index"
@click="changeChartType(type)"
:class="{ active: type.value === states.chartType }"
>
{{ type.label }}
</span>
</div>
</div>
<div class="graph-styles item">
<label>Graph styles</label>
</div> </div>
<div class="item"> <div class="item">
<label>Widget options</label> <span class="label">Tooltip</span>
</div> <el-input
<div class="item"> class="input"
<label>Standard options</label> v-model="tooltip"
</div> size="mini"
</div> placeholder="Please input tooltip"
<div class="footer"> />
<el-button size="mini">
{{ t("cancel") }}
</el-button>
<el-button size="mini" type="primary">
{{ t("apply") }}
</el-button>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { reactive, defineComponent } from "vue"; import { ElInput } from "element-plus";
import { useI18n } from "vue-i18n"; import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard"; const title = ref<string>("");
import { useAppStoreWithOut } from "@/store/modules/app"; const tooltip = ref<string>("");
import { ElMessage, ElButton } from "element-plus";
import { ValuesTypes, MetricQueryTypes, ChartTypes } from "../data";
import { Option } from "@/types/app";
import Loading from "@/utils/loading";
import graphs from "../graphs";
import controls from "../controls";
export default defineComponent({
name: "WidgetConfig",
components: { ...graphs, ...controls, ElButton },
setup() {
const states = reactive<{
metrics: string;
valueTypes: Option[];
valueType: string;
metricQueryType: string;
chartType: string;
}>({
metrics: "",
valueTypes: [],
valueType: "",
metricQueryType: "",
chartType: "Bar",
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const appStoreWithOut = useAppStoreWithOut();
const { loading } = Loading();
async function changeMetrics(val: Option[]) {
if (!val.length) {
states.valueTypes = [];
states.valueType = "";
return;
}
const loadingInstance = loading({ text: t("loading"), fullscreen: true });
const resp = await dashboardStore.fetchMetricType(val[0].value);
loadingInstance.close();
if (resp.error) {
ElMessage.error(resp.data.error);
return;
}
const { typeOfMetrics } = resp.data;
states.valueTypes = ValuesTypes[typeOfMetrics];
states.valueType = ValuesTypes[typeOfMetrics][0].value;
}
function changeValueType(val: Option[]) {
states.valueType = String(val[0].value);
states.metricQueryType = (MetricQueryTypes as any)[states.valueType];
}
function changeChartType(item: Option) {
states.chartType = String(item.value);
}
const metricOpts = [
{ value: "service_apdex", label: "service_apdex" },
{ value: "service_sla", label: "service_sla" },
{ value: "service_cpm", label: "service_cpm" },
{ value: "service_resp_time", label: "service_resp_time" },
{ value: "service_percentile", label: "service_percentile" },
{
value: "service_mq_consume_latency",
label: "service_mq_consume_latency",
},
{ value: "service_mq_consume_count", label: "service_mq_consume_count" },
];
const source = {
count: [1, 2, 5, 4, 5, 6, 7, 3, 4, 5, 2, 1, 6, 9],
avg: [3, 2, 4, 4, 5, 6, 5, 3, 4, 1, 2, 1, 6, 10],
};
return {
states,
changeChartType,
changeValueType,
changeMetrics,
t,
appStoreWithOut,
ChartTypes,
source,
metricOpts,
};
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.widget-config { .label {
position: relative; font-size: 13px;
width: 100%; font-weight: 500;
} display: block;
.item {
margin: 20px 0;
}
.graph {
min-width: 1280px;
border: 1px solid #eee;
padding: 10px;
}
label {
display: inline-block;
font-weight: bold;
margin-bottom: 10px;
}
.selectors {
width: 500px;
margin-right: 10px;
}
.name {
font-size: 12px;
font-weight: bold;
margin-bottom: 5px; margin-bottom: 5px;
} }
.config { .input {
min-width: 1280px; width: 500px;
} }
.chart-types { .item {
span { margin-bottom: 10px;
display: inline-block;
padding: 5px 10px;
border: 1px solid #ccc;
background-color: #fff;
border-right: 0;
cursor: pointer;
}
span:nth-last-child(1) {
border-right: 1px solid #ccc;
}
}
span.active {
background-color: #eee;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
} }
</style> </style>

View File

@ -0,0 +1,18 @@
<!-- 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>bar config</template>
<script lang="ts" setup>
import { ref } from "vue";
</script>

View File

@ -0,0 +1,61 @@
<!-- 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>
<span class="label">Bar Width</span>
<el-slider
class="bar-width"
v-model="barWidth"
show-input
input-size="small"
/>
</div>
<div>
<span class="label">Show Background</span>
<el-switch
v-model="showBackground"
active-text="Yes"
inactive-text="No"
></el-switch>
</div>
</template>
<script lang="ts" setup>
import { defineProps, ref } from "vue";
import type { PropType } from "vue";
import { BarConfig } from "./types";
import { ElSlider, ElSwitch } from "element-plus";
const props = defineProps({
config: {
type: Object as PropType<BarConfig>,
default: () => ({ showBackground: true, barWidth: 30 }),
},
});
const barWidth = ref(props.config.barWidth);
const showBackground = ref(props.config.showBackground);
</script>
<style lang="scss" scoped>
.bar-width {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@ -0,0 +1,18 @@
<!-- 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>bar config</template>
<script lang="ts" setup>
import { ref } from "vue";
</script>

View File

@ -0,0 +1,26 @@
/**
* 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.
*/
import AreaConfig from "./Area.vue";
import LineConfig from "./Line.vue";
import BarConfig from "./Bar.vue";
export default {
AreaConfig,
LineConfig,
BarConfig,
};

View File

@ -0,0 +1,20 @@
/**
* 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 interface BarConfig {
showBackground: boolean;
barWidth: number;
}

View File

@ -21,7 +21,7 @@ export const ChartTypes = [
{ label: "Heatmap", value: "Heatmap" }, { label: "Heatmap", value: "Heatmap" },
{ label: "Pie", value: "Pie" }, { label: "Pie", value: "Pie" },
{ label: "Card", value: "Card" }, { label: "Card", value: "Card" },
{ label: "Progress Bar", value: "ProgressBar" }, { label: "Top List", value: "TopList" },
{ label: "Table", value: "Table" }, { label: "Table", value: "Table" },
{ label: "Endpoint List", value: "EndpointList" }, { label: "Endpoint List", value: "EndpointList" },
{ label: "Instance List", value: "InstanceList" }, { label: "Instance List", value: "InstanceList" },

View File

@ -32,9 +32,9 @@ const props = defineProps({
type: Object as PropType<{ type: Object as PropType<{
theme: string; theme: string;
showBackground: boolean; showBackground: boolean;
barMaxWidth: number; barWidth: number;
}>, }>,
default: () => ({ theme: "light", showBackground: true, barMaxWidth: 30 }), default: () => ({ theme: "light", showBackground: true, barWidth: 20 }),
}, },
}); });
/*global Nullable */ /*global Nullable */
@ -80,7 +80,7 @@ function getOption() {
name: i, name: i,
type: "bar", type: "bar",
symbol: "none", symbol: "none",
barWidth: props.config.barMaxWidth, barWidth: props.config.barWidth,
stack: "sum", stack: "sum",
lineStyle: { lineStyle: {
width: 1.5, width: 1.5,

View File

@ -19,7 +19,7 @@ import Area from "./Area.vue";
import Line from "./Line.vue"; import Line from "./Line.vue";
import Bar from "./Bar.vue"; import Bar from "./Bar.vue";
import Heatmap from "./Heatmap.vue"; import Heatmap from "./Heatmap.vue";
import ProgressBar from "./ProgressBar.vue"; import TopList from "./TopList.vue";
import Table from "./Table.vue"; import Table from "./Table.vue";
import Pie from "./Pie.vue"; import Pie from "./Pie.vue";
import Card from "./Card.vue"; import Card from "./Card.vue";
@ -28,7 +28,7 @@ export default {
Line, Line,
Bar, Bar,
Heatmap, Heatmap,
ProgressBar, TopList,
Area, Area,
Table, Table,
Pie, Pie,