You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2023/02/06 05:38:27 UTC
[skywalking-booster-ui] branch main updated: feat: Implement independent mode for widgets (#221)
This is an automated email from the ASF dual-hosted git repository.
wusheng 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 224053c feat: Implement independent mode for widgets (#221)
224053c is described below
commit 224053c0f4dbcba5db67ad20bee73f01895b8a2f
Author: Fine0830 <fa...@gmail.com>
AuthorDate: Mon Feb 6 13:38:19 2023 +0800
feat: Implement independent mode for widgets (#221)
---
package.json | 10 +-
src/App.vue | 13 +-
src/layout/components/NavBar.vue | 2 +-
src/layout/components/SideBar.vue | 14 +-
src/locales/lang/en.ts | 2 +
src/locales/lang/es.ts | 2 +
src/locales/lang/zh.ts | 2 +
src/router/dashboard.ts | 15 ++
src/store/modules/dashboard.ts | 5 +
src/store/modules/selectors.ts | 6 +-
src/styles/lib.scss | 8 ++
src/views/dashboard/Edit.vue | 11 +-
src/views/dashboard/List.vue | 2 +
src/views/dashboard/Widget.vue | 188 ++++++++++++++++++++++++++
src/views/dashboard/components/WidgetLink.vue | 125 +++++++++++++++++
src/views/dashboard/controls/Widget.vue | 14 +-
16 files changed, 403 insertions(+), 16 deletions(-)
diff --git a/package.json b/package.json
index a84b375..b15fa53 100644
--- a/package.json
+++ b/package.json
@@ -85,10 +85,14 @@
"not dead"
],
"lint-staged": {
- "*.{js,jsx,ts,tsx,vue,scss,less}": [
- "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+ "*.{js,jsx,ts,tsx,vue}": [
+ "eslint . --ext .vue,.js,.jsx,.ts,.tsx --fix --ignore-path .gitignore",
"prettier --write \"src/**/*.{js,tsx,css,less,scss,vue,html,md}\"",
- "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"
+ "stylelint --cache --fix \"**/*.{vue}\" --cache --cache-location node_modules/.cache/stylelint/"
+ ],
+ "*.{scss,less}": [
+ "prettier --write \"src/**/*.{js,tsx,css,less,scss,vue,html,md}\"",
+ "stylelint --cache --fix \"**/*.{less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"
],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write"
diff --git a/src/App.vue b/src/App.vue
index 194f4c0..93c3bce 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -15,11 +15,22 @@ limitations under the License. -->
<template>
<router-view />
</template>
+<script lang="ts" setup>
+ import { useRoute } from "vue-router";
+ const route = useRoute();
+
+ setTimeout(() => {
+ if (route.name === "ViewWidget") {
+ (document.querySelector("#app") as any).style.minWidth = "120px";
+ } else {
+ (document.querySelector("#app") as any).style.minWidth = "1024px";
+ }
+ }, 500);
+</script>
<style>
#app {
color: #2c3e50;
height: 100%;
overflow: hidden;
- min-width: 1024px;
}
</style>
diff --git a/src/layout/components/NavBar.vue b/src/layout/components/NavBar.vue
index fc9c052..d50cc28 100644
--- a/src/layout/components/NavBar.vue
+++ b/src/layout/components/NavBar.vue
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="nav-bar flex-h">
- <div class="title">{{ appStore.pageTitle || t(pageName) }}</div>
+ <div class="title">{{ route.name === "ViewWidget" ? "" : appStore.pageTitle || t(pageName) }}</div>
<div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
<TimePicker
diff --git a/src/layout/components/SideBar.vue b/src/layout/components/SideBar.vue
index 4e0e79f..d27c2db 100644
--- a/src/layout/components/SideBar.vue
+++ b/src/layout/components/SideBar.vue
@@ -13,7 +13,7 @@ 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="side-bar">
+ <div class="side-bar" v-if="showMenu">
<div :class="isCollapse ? 'logo-icon-collapse' : 'logo-icon'">
<Icon :size="isCollapse ? 'xl' : 'logo'" :iconName="isCollapse ? 'logo' : 'logo-sw'" />
</div>
@@ -76,7 +76,7 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref } from "vue";
import type { RouteRecordRaw } from "vue-router";
- import { useRouter } from "vue-router";
+ import { useRouter, useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import Icon from "@/components/Icon.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
@@ -86,12 +86,18 @@ limitations under the License. -->
const name = ref<string>(String(useRouter().currentRoute.value.name));
const theme = ["VirtualMachine", "Kubernetes"].includes(name.value || "") ? ref("light") : ref("black");
const routes = ref<RouteRecordRaw[] | any>(useRouter().options.routes);
+ const route = useRoute();
+ const isCollapse = ref(false);
+ const showMenu = ref(true);
+
if (/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
appStore.setIsMobile(true);
} else {
appStore.setIsMobile(false);
}
- const isCollapse = ref(false);
+ if (route.name === "ViewWidget") {
+ showMenu.value = false;
+ }
const controlMenu = () => {
isCollapse.value = !isCollapse.value;
};
@@ -119,7 +125,7 @@ limitations under the License. -->
.logo-icon-collapse {
width: 65px;
- margin: 15px 0 10px 0;
+ margin: 5px 0 10px 0;
text-align: center;
}
diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts
index 60c6c9a..6e4a94d 100644
--- a/src/locales/lang/en.ts
+++ b/src/locales/lang/en.ts
@@ -181,6 +181,8 @@ const msg = {
taskTitle: "HTTP request and response collecting rules",
iframeWidgetTip: "Add a link to a widget",
iframeSrc: "Iframe Link",
+ generateLink: "Generate Link",
+ setDuration: "Set Duration",
seconds: "Seconds",
hourTip: "Select Hour",
minuteTip: "Select Minute",
diff --git a/src/locales/lang/es.ts b/src/locales/lang/es.ts
index 3cbcd4c..d53eb5a 100644
--- a/src/locales/lang/es.ts
+++ b/src/locales/lang/es.ts
@@ -164,6 +164,8 @@ const msg = {
legendValues: "Valor de la leyenda",
iframeWidgetTip: "Añadir enlaces a los gadgets",
iframeSrc: "Enlace Iframe",
+ generateLink: "Generar enlaces",
+ setDuration: "Establecer la duración",
seconds: "Segundos",
hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto",
diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts
index 59f7cdf..e426c20 100644
--- a/src/locales/lang/zh.ts
+++ b/src/locales/lang/zh.ts
@@ -178,6 +178,8 @@ const msg = {
taskTitle: "HTTP请求和响应收集规则",
iframeWidgetTip: "添加widget的链接",
iframeSrc: "Iframe链接",
+ generateLink: "生成链接",
+ setDuration: "设置时间区间",
seconds: "秒",
hourTip: "选择小时",
minuteTip: "选择分钟",
diff --git a/src/router/dashboard.ts b/src/router/dashboard.ts
index 4c7b558..f4f799c 100644
--- a/src/router/dashboard.ts
+++ b/src/router/dashboard.ts
@@ -176,6 +176,21 @@ export const routesDashboard: Array<RouteRecordRaw> = [
},
],
},
+ {
+ path: "",
+ name: "Widget",
+ component: () => import("@/views/dashboard/Widget.vue"),
+ meta: {
+ notShow: true,
+ },
+ children: [
+ {
+ path: "/page/:layer/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:config/:duration?",
+ component: () => import("@/views/dashboard/Widget.vue"),
+ name: "ViewWidget",
+ },
+ ],
+ },
],
},
];
diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts
index 7a20c78..eee2b37 100644
--- a/src/store/modules/dashboard.ts
+++ b/src/store/modules/dashboard.ts
@@ -40,6 +40,7 @@ interface DashboardState {
currentDashboard: Nullable<DashboardItem>;
editMode: boolean;
currentTabIndex: number;
+ showLinkConfig: boolean;
}
export const dashboardStore = defineStore({
@@ -58,6 +59,7 @@ export const dashboardStore = defineStore({
currentDashboard: null,
editMode: false,
currentTabIndex: 0,
+ showLinkConfig: false,
}),
actions: {
setLayout(data: LayoutConfig[]) {
@@ -66,6 +68,9 @@ export const dashboardStore = defineStore({
setMode(mode: boolean) {
this.editMode = mode;
},
+ setWidgetLink(show: boolean) {
+ this.showLinkConfig = show;
+ },
resetDashboards(list: DashboardItem[]) {
this.dashboards = list;
sessionStorage.setItem("dashboards", JSON.stringify(list));
diff --git a/src/store/modules/selectors.ts b/src/store/modules/selectors.ts
index ae83609..580aba7 100644
--- a/src/store/modules/selectors.ts
+++ b/src/store/modules/selectors.ts
@@ -211,12 +211,12 @@ export const selectorStore = defineStore({
return res.data;
},
- async getProcess(instanceId: string, isRelation?: boolean) {
- if (!instanceId) {
+ async getProcess(processId: string, isRelation?: boolean) {
+ if (!processId) {
return;
}
const res: AxiosResponse = await graphql.query("queryProcess").params({
- instanceId,
+ processId,
});
if (!res.data.errors) {
if (isRelation) {
diff --git a/src/styles/lib.scss b/src/styles/lib.scss
index f991ab4..fd7ebbf 100644
--- a/src/styles/lib.scss
+++ b/src/styles/lib.scss
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
.flex-v {
display: flex;
flex-direction: column;
@@ -102,6 +103,13 @@
.mt-0 {
margin-top: 0;
}
+.mt-10 {
+ margin-top: 10px;
+}
+
+.mt-20 {
+ margin-top: 20px;
+}
.mb-5 {
margin-bottom: 5px;
diff --git a/src/views/dashboard/Edit.vue b/src/views/dashboard/Edit.vue
index a258ac3..7b78e9e 100644
--- a/src/views/dashboard/Edit.vue
+++ b/src/views/dashboard/Edit.vue
@@ -30,6 +30,13 @@ limitations under the License. -->
>
<component :is="dashboardStore.selectedGrid.type" />
</el-dialog>
+ <el-dialog
+ v-model="dashboardStore.showLinkConfig"
+ :destroy-on-close="true"
+ @closed="dashboardStore.setWidgetLink(false)"
+ >
+ <WidgetLink />
+ </el-dialog>
</div>
</template>
<script lang="ts">
@@ -42,16 +49,18 @@ limitations under the License. -->
import { useAppStoreWithOut } from "@/store/modules/app";
import Configuration from "./configuration";
import type { LayoutConfig } from "@/types/dashboard";
+ import WidgetLink from "./components/WidgetLink.vue";
export default defineComponent({
name: "Dashboard",
- components: { ...Configuration, GridLayout, Tool },
+ components: { ...Configuration, GridLayout, Tool, WidgetLink },
setup() {
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const p = useRoute().params;
const layoutKey = ref<string>(`${p.layerId}_${p.entity}_${p.name}`);
+
setTemplate();
async function setTemplate() {
await dashboardStore.setDashboards();
diff --git a/src/views/dashboard/List.vue b/src/views/dashboard/List.vue
index 20a3f75..4029bbc 100644
--- a/src/views/dashboard/List.vue
+++ b/src/views/dashboard/List.vue
@@ -226,6 +226,7 @@ limitations under the License. -->
standard?: unknown;
label?: string;
value?: string;
+ filters?: unknown;
})[],
) {
for (const child of children || []) {
@@ -235,6 +236,7 @@ limitations under the License. -->
delete child.id;
delete child.label;
delete child.value;
+ delete child.filters;
if (isEmptyObject(child.graph)) {
delete child.graph;
}
diff --git a/src/views/dashboard/Widget.vue b/src/views/dashboard/Widget.vue
new file mode 100644
index 0000000..bfdbff2
--- /dev/null
+++ b/src/views/dashboard/Widget.vue
@@ -0,0 +1,188 @@
+<!-- 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="content">
+ <div class="header">
+ <span>{{ decodeURIComponent(title) }}</span>
+ <div class="tips" v-show="tips">
+ <el-tooltip :content="decodeURIComponent(tips) || ''">
+ <span>
+ <Icon iconName="info_outline" size="sm" />
+ </span>
+ </el-tooltip>
+ </div>
+ </div>
+ <div class="widget-chart" :style="{ height: config.height - 60 + 'px' }">
+ <component
+ :is="graph.type"
+ :intervalTime="appStoreWithOut.intervalTime"
+ :data="source"
+ :config="{
+ i: 0,
+ ...graph,
+ metrics: config.metrics,
+ metricTypes: config.metricTypes,
+ metricConfig: config.metricConfig,
+ }"
+ :needQuery="true"
+ />
+ <div v-show="!config.type" class="no-data">
+ {{ t("noData") }}
+ </div>
+ </div>
+ </div>
+</template>
+<script lang="ts">
+ import { computed, ref, defineComponent, watch } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useAppStoreWithOut } from "@/store/modules/app";
+ import { useRoute } from "vue-router";
+ import { useSelectorStore } from "@/store/modules/selectors";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import { useQueryProcessor, useSourceProcessor, useGetMetricEntity } from "@/hooks/useMetricsProcessor";
+ import graphs from "./graphs";
+ import { EntityType } from "./data";
+
+ export default defineComponent({
+ name: "WidgetPage",
+ components: {
+ ...graphs,
+ },
+ setup() {
+ const { t } = useI18n();
+ const appStoreWithOut = useAppStoreWithOut();
+ const selectorStore = useSelectorStore();
+ const route = useRoute();
+ const config = computed<any>(() => JSON.parse(decodeURIComponent(route.params.config as string) as string));
+ const graph = computed(() => config.value.graph || {});
+ const source = ref<unknown>({});
+ const loading = ref<boolean>(false);
+ const dashboardStore = useDashboardStore();
+ const title = computed(() => (config.value.widget && config.value.widget.title) || "");
+ const tips = computed(() => (config.value.widget && config.value.widget.tips) || "");
+
+ init();
+ async function init() {
+ dashboardStore.setLayer(route.params.layer);
+ dashboardStore.setEntity(route.params.entity);
+ await setSelector();
+ await queryMetrics();
+ }
+ async function setSelector() {
+ const { serviceId, podId, processId, destServiceId, destPodId, destProcessId, entity } = route.params;
+ if (serviceId) {
+ await selectorStore.getService(serviceId);
+ }
+ if (
+ [EntityType[4].value, EntityType[5].value, EntityType[6].value, EntityType[7].value].includes(
+ entity as string,
+ )
+ ) {
+ await selectorStore.getService(destServiceId, true);
+ }
+ if ([EntityType[3].value, EntityType[5].value, EntityType[7].value].includes(entity as string)) {
+ await selectorStore.getInstance(podId);
+ }
+ if ([EntityType[2].value, EntityType[6].value].includes(entity as string)) {
+ await selectorStore.getEndpoint(podId);
+ }
+ if (EntityType[6].value === entity) {
+ await selectorStore.getEndpoint(destPodId, true);
+ }
+ if ([EntityType[5].value, EntityType[7].value].includes(entity as string)) {
+ await selectorStore.getInstance(destPodId, true);
+ }
+ if (EntityType[7].value === entity) {
+ selectorStore.getProcess(processId);
+ selectorStore.getProcess(destProcessId, true);
+ }
+ }
+ async function queryMetrics() {
+ const metricTypes = config.value.metricTypes || [];
+ const metrics = config.value.metrics || [];
+ const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
+ const params = await useQueryProcessor({ ...config.value, catalog });
+ if (!params) {
+ source.value = {};
+ return;
+ }
+ loading.value = true;
+ const json = await dashboardStore.fetchMetricValue(params);
+ loading.value = false;
+ if (!json) {
+ return;
+ }
+ const d = {
+ metrics: config.value.metrics || [],
+ metricTypes: config.value.metricTypes || [],
+ metricConfig: config.value.metricConfig || [],
+ };
+ source.value = useSourceProcessor(json, d);
+ }
+ watch(
+ () => appStoreWithOut.durationTime,
+ () => {
+ queryMetrics();
+ },
+ );
+ return {
+ t,
+ graph,
+ source,
+ appStoreWithOut,
+ config,
+ title,
+ tips,
+ };
+ },
+ });
+</script>
+<style lang="scss" scoped>
+ .content {
+ min-width: 100px;
+ border: 1px solid #eee;
+ background-color: #fff;
+ position: relative;
+ }
+
+ .widget-chart {
+ background: #fff;
+ box-shadow: 0px 1px 4px 0px #00000029;
+ border-radius: 3px;
+ padding: 5px;
+ width: 100%;
+ }
+
+ .no-data {
+ font-size: 14px;
+ text-align: center;
+ line-height: 400px;
+ }
+
+ .header {
+ height: 25px;
+ line-height: 25px;
+ text-align: center;
+ background-color: aliceblue;
+ font-size: 12px;
+ position: relative;
+ }
+
+ .tips {
+ position: absolute;
+ right: 5px;
+ top: 0;
+ }
+</style>
diff --git a/src/views/dashboard/components/WidgetLink.vue b/src/views/dashboard/components/WidgetLink.vue
new file mode 100644
index 0000000..4e5c343
--- /dev/null
+++ b/src/views/dashboard/components/WidgetLink.vue
@@ -0,0 +1,125 @@
+<!-- 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="link-content">
+ <div>
+ <label class="mr-5">{{ t("setDuration") }}</label>
+ <el-switch v-model="hasDuration" />
+ </div>
+ <div v-if="hasDuration">
+ <label class="mr-20">{{ t("duration") }}</label>
+ <TimePicker
+ :value="[appStore.durationRow.start, appStore.durationRow.end]"
+ position="bottom"
+ format="YYYY-MM-DD HH:mm"
+ @input="changeTimeRange"
+ />
+ </div>
+ <el-button size="small" type="primary" class="mt-20" @click="getLink">{{ t("generateLink") }}</el-button>
+ <div v-show="widgetLink" class="mt-10">
+ <span @click="viewPage" class="link">
+ {{ host + widgetLink }}
+ </span>
+ <span>
+ <Icon class="cp ml-10" iconName="copy" @click="copyLink" />
+ </span>
+ </div>
+ </div>
+</template>
+<script lang="ts" setup>
+ import { ref } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useAppStoreWithOut } from "@/store/modules/app";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import { useSelectorStore } from "@/store/modules/selectors";
+ import router from "@/router";
+ import copy from "@/utils/copy";
+
+ const { t } = useI18n();
+ const appStore = useAppStoreWithOut();
+ const dashboardStore = useDashboardStore();
+ const selectorStore = useSelectorStore();
+ const hasDuration = ref<boolean>(true);
+ const widgetLink = ref<string>("");
+ const dates = ref<Date[]>([]);
+ const host = window.location.host;
+
+ function changeTimeRange(val: Date[] | any) {
+ dates.value = val;
+ }
+ function getLink() {
+ if (!dashboardStore.selectedGrid) {
+ return;
+ }
+ const serviceId = selectorStore.currentService ? selectorStore.currentService.id : null;
+ const podId = selectorStore.currentPod ? selectorStore.currentPod.id : null;
+ const processId = selectorStore.currentProcess ? selectorStore.currentProcess.id : null;
+ const destServiceId = selectorStore.currentDestService ? selectorStore.currentDestService.id : null;
+ const destPodId = selectorStore.currentDestPod ? selectorStore.currentDestPod.id : null;
+ const destProcessId = selectorStore.currentDestProcess ? selectorStore.currentDestProcess.id : null;
+ const duration = JSON.stringify({
+ start: dates.value[0] ? new Date(dates.value[0]).getTime() : appStore.durationRow.start.getTime(),
+ end: dates.value[1] ? new Date(dates.value[1]).getTime() : appStore.durationRow.end.getTime(),
+ step: appStore.durationRow.step,
+ utc: appStore.utc,
+ });
+ const w = {
+ title: encodeURIComponent(dashboardStore.selectedGrid.widget.title || ""),
+ tips: encodeURIComponent(dashboardStore.selectedGrid.widget.tips || ""),
+ };
+ const metricConfig = (dashboardStore.selectedGrid.metricConfig || []).map((d: any) => {
+ if (d.label) {
+ d.label = encodeURIComponent(d.label);
+ }
+ if (d.unit) {
+ d.unit = encodeURIComponent(d.unit);
+ }
+ return d;
+ });
+ const config = JSON.stringify({
+ type: dashboardStore.selectedGrid.type,
+ widget: w,
+ graph: dashboardStore.selectedGrid.graph,
+ metrics: dashboardStore.selectedGrid.metrics,
+ metricTypes: dashboardStore.selectedGrid.metricTypes,
+ metricConfig: metricConfig,
+ height: dashboardStore.selectedGrid.h * 20 + 60,
+ });
+ const path = `/page/${dashboardStore.layerId}/${
+ dashboardStore.entity
+ }/${serviceId}/${podId}/${processId}/${destServiceId}/${destPodId}/${destProcessId}/${encodeURIComponent(config)}`;
+ widgetLink.value = hasDuration.value ? `${path}/${encodeURIComponent(duration)}` : path;
+ }
+ function viewPage() {
+ const routeUrl = router.resolve({ path: widgetLink.value });
+ window.open(routeUrl.href, "_blank");
+ }
+ function copyLink() {
+ copy(host + widgetLink.value);
+ }
+</script>
+<style lang="scss" scoped>
+ .link {
+ color: #409eff;
+ cursor: pointer;
+ }
+
+ .link-content {
+ height: 300px;
+ font-size: 12px;
+ overflow: auto;
+ padding-bottom: 10px;
+ }
+</style>
diff --git a/src/views/dashboard/controls/Widget.vue b/src/views/dashboard/controls/Widget.vue
index cb38795..afab659 100644
--- a/src/views/dashboard/controls/Widget.vue
+++ b/src/views/dashboard/controls/Widget.vue
@@ -26,18 +26,21 @@ limitations under the License. -->
<Icon iconName="info_outline" size="sm" class="operation" v-show="widget.tips" />
</span>
</el-tooltip>
- <el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
+ <el-popover placement="bottom" trigger="click" :width="100">
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
- <div class="tools" @click="editConfig">
+ <div class="tools" @click="editConfig" v-if="dashboardStore.editMode">
<span>{{ t("edit") }}</span>
</div>
- <div class="tools" @click="removeWidget">
+ <div class="tools" @click="removeWidget" v-if="dashboardStore.editMode">
<span>{{ t("delete") }}</span>
</div>
+ <div class="tools" @click="generateLink">
+ <span>{{ t("generateLink") }}</span>
+ </div>
</el-popover>
</div>
</div>
@@ -161,6 +164,10 @@ limitations under the License. -->
}
}
}
+ function generateLink() {
+ dashboardStore.setWidgetLink(true);
+ dashboardStore.selectWidget(props.data);
+ }
watch(
() => [props.data.metricTypes, props.data.metrics],
() => {
@@ -227,6 +234,7 @@ limitations under the License. -->
state,
appStore,
removeWidget,
+ generateLink,
editConfig,
data,
loading,