Merge branch 'main' of github.com:apache/skywalking-booster-ui into feat/topology

This commit is contained in:
Fine 2024-01-11 10:18:21 +08:00
commit 1ef2fb3220
8 changed files with 197 additions and 40 deletions

12
package-lock.json generated
View File

@ -7167,9 +7167,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -20411,9 +20411,9 @@
"dev": true "dev": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.15.2", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw=="
}, },
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",

View File

@ -48,8 +48,14 @@ limitations under the License. -->
@input="changeTimeRange" @input="changeTimeRange"
/> />
<span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span> <span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span>
<span class="ml-5"> <span class="ml-5" ref="themeSwitchRef">
<el-switch v-model="theme" :active-icon="Moon" :inactive-icon="Sunny" inline-prompt @change="changeTheme" /> <el-switch
v-model="theme"
:active-icon="Moon"
:inactive-icon="Sunny"
inline-prompt
@change="handleChangeTheme"
/>
</span> </span>
<span title="refresh" class="ghost ml-5 cp" @click="handleReload"> <span title="refresh" class="ghost ml-5 cp" @click="handleReload">
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" /> <Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
@ -67,18 +73,18 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { Themes } from "@/constants/data";
import { useRoute } from "vue-router"; import router from "@/router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { MetricCatalog } from "@/views/dashboard/data";
import type { DashboardItem } from "@/types/dashboard"; import type { DashboardItem } from "@/types/dashboard";
import router from "@/router"; import timeFormat from "@/utils/timeFormat";
import { MetricCatalog } from "@/views/dashboard/data";
import { ArrowRight, Moon, Sunny } from "@element-plus/icons-vue"; import { ArrowRight, Moon, Sunny } from "@element-plus/icons-vue";
import { Themes } from "@/constants/data"; import { ElMessage } from "element-plus";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
/*global Indexable */ /*global Indexable */
const { t, te } = useI18n(); const { t, te } = useI18n();
@ -89,6 +95,7 @@ limitations under the License. -->
const timeRange = ref<number>(0); const timeRange = ref<number>(0);
const pageTitle = ref<string>(""); const pageTitle = ref<string>("");
const theme = ref<boolean>(true); const theme = ref<boolean>(true);
const themeSwitchRef = ref<HTMLElement>();
const savedTheme = window.localStorage.getItem("theme-is-dark"); const savedTheme = window.localStorage.getItem("theme-is-dark");
if (savedTheme === "false") { if (savedTheme === "false") {
@ -119,6 +126,35 @@ limitations under the License. -->
window.localStorage.setItem("theme-is-dark", String(theme.value)); window.localStorage.setItem("theme-is-dark", String(theme.value));
} }
function handleChangeTheme() {
const x = themeSwitchRef.value?.offsetLeft ?? 0;
const y = themeSwitchRef.value?.offsetTop ?? 0;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
// compatibility handling
if (!document.startViewTransition) {
changeTheme();
return;
}
// api: https://developer.chrome.com/docs/web-platform/view-transitions
const transition = document.startViewTransition(() => {
changeTheme();
});
transition.ready.then(() => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: !theme.value ? clipPath.reverse() : clipPath,
},
{
duration: 500,
easing: "ease-in",
pseudoElement: !theme.value ? "::view-transition-old(root)" : "::view-transition-new(root)",
},
);
});
}
function getName(list: any[]) { function getName(list: any[]) {
return list.find((d: any) => d.selected) || {}; return list.find((d: any) => d.selected) || {};
} }

View File

@ -94,8 +94,8 @@ const msg = {
editTab: "Enable editing tab names", editTab: "Enable editing tab names",
label: "Service Name", label: "Service Name",
id: "Service ID", id: "Service ID",
setRoot: "Set this to root", setRoot: "Set Normal to Root",
setNormal: "Set this to normal", setNormal: "Set Root to Normal",
export: "Export Dashboard Templates", export: "Export Dashboard Templates",
import: "Import Dashboard Templates", import: "Import Dashboard Templates",
yes: "Yes", yes: "Yes",

View File

@ -47,7 +47,23 @@ router.beforeEach((to, from, next) => {
} }
if (to.path === "/") { if (to.path === "/") {
const defaultPath = (routesLayers[0] && routesLayers[0].children[0].path) || ""; let defaultPath = "";
for (const route of routesLayers) {
for (const child of route.children) {
if (child.meta.activate) {
defaultPath = child.path;
break;
}
}
if (defaultPath) {
break;
}
}
if (!defaultPath) {
defaultPath = "/marketplace";
}
next({ path: defaultPath }); next({ path: defaultPath });
} else { } else {
next(); next();

View File

@ -260,3 +260,27 @@ div:has(> a.menu-title) {
.el-input-number .el-input__inner { .el-input-number .el-input__inner {
text-align: left !important; text-align: left !important;
} }
html {
&::view-transition-old(root),
&::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
&.dark {
&::view-transition-old(root) {
z-index: 1;
}
&::view-transition-new(root) {
z-index: 999;
}
}
&::view-transition-old(root) {
z-index: 999;
}
&::view-transition-new(root) {
z-index: 1;
}
}

View File

@ -21,6 +21,7 @@ export type DashboardItem = {
layer: string; layer: string;
isRoot: boolean; isRoot: boolean;
name: string; name: string;
isDefault: boolean;
}; };
export interface LayoutConfig { export interface LayoutConfig {
x: number; x: number;

View File

@ -15,11 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import type { import type {
ComponentPublicInstance,
ComponentRenderProxy, ComponentRenderProxy,
FunctionalComponent,
VNode, VNode,
VNodeChild, VNodeChild,
ComponentPublicInstance,
FunctionalComponent,
PropType as VuePropType, PropType as VuePropType,
} from "vue"; } from "vue";
@ -39,6 +39,11 @@ declare global {
lastBuildTime: string; lastBuildTime: string;
}; };
// Document
interface Document {
startViewTransition(callback: () => void): any;
}
// vue // vue
declare type PropType<T> = VuePropType<T>; declare type PropType<T> = VuePropType<T>;
declare type VueNode = VNodeChild | JSX.Element; declare type VueNode = VNodeChild | JSX.Element;

View File

@ -57,14 +57,39 @@ limitations under the License. -->
</el-table-column> </el-table-column>
<el-table-column prop="layer" label="Layer" width="160" /> <el-table-column prop="layer" label="Layer" width="160" />
<el-table-column prop="entity" label="Entity" width="200" /> <el-table-column prop="entity" label="Entity" width="200" />
<el-table-column prop="isRoot" label="Root" width="60"> <el-table-column prop="isRoot" label="Root" width="150">
<template #default="scope"> <template #default="scope">
<span> <el-popconfirm
{{ scope.row.isRoot ? t("yes") : t("no") }} :title="t('rootTitle')"
</span> @confirm="setRoot(scope.row)"
v-if="[EntityType[0].value, EntityType[1].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 110px">
{{ scope.row.isRoot ? t("setNormal") : t("setRoot") }}
</el-button>
</template>
</el-popconfirm>
<span v-else> -- </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="Operations" width="350"> <el-table-column prop="isDefault" label="Default Dashboard" width="140">
<template #default="scope">
<el-popconfirm
:title="t('rootTitle')"
@confirm="handleTopLevel(scope.row)"
v-if="[EntityType[0].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 80px">
{{ scope.row.isDefault ? "Disable" : "Enable" }}
</el-button>
</template>
</el-popconfirm>
<span v-else> -- </span>
</template>
</el-table-column>
<el-table-column label="Operations" width="300">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)"> <el-button size="small" @click="handleEdit(scope.row)">
{{ t("edit") }} {{ t("edit") }}
@ -79,17 +104,6 @@ limitations under the License. -->
</el-button> </el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
<el-popconfirm
:title="t('rootTitle')"
@confirm="setRoot(scope.row)"
v-if="[EntityType[0].value, EntityType[1].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 110px" type="danger">
{{ scope.row.isRoot ? t("setNormal") : t("setRoot") }}
</el-button>
</template>
</el-popconfirm>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -175,17 +189,19 @@ limitations under the License. -->
} }
loading.value = true; loading.value = true;
for (const item of arr) { for (const item of arr) {
const { layer, name, entity, isRoot, children } = item.configuration; const { layer, name, entity, isRoot, children, isDefault } = item.configuration;
const index = dashboardStore.dashboards.findIndex((d: DashboardItem) => d.id === item.id); const index = dashboardStore.dashboards.findIndex((d: DashboardItem) => d.id === item.id);
const p: DashboardItem = { const p: DashboardItem = {
name: name.split(" ").join("-"), name: name.split(" ").join("-"),
layer: layer, layer: layer,
entity: entity, entity: entity,
isRoot: false, isRoot: false,
isDefault: false,
}; };
if (index > -1) { if (index > -1) {
p.id = item.id; p.id = item.id;
p.isRoot = isRoot; p.isRoot = isRoot;
p.isDefault = isDefault;
} }
dashboardStore.setCurrentDashboard(p); dashboardStore.setCurrentDashboard(p);
dashboardStore.setLayout(children); dashboardStore.setLayout(children);
@ -328,7 +344,7 @@ limitations under the License. -->
configuration: JSON.stringify(c), configuration: JSON.stringify(c),
}; };
const res = await dashboardStore.updateDashboard(setting); const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) { if (res.data.changeTemplate.status) {
sessionStorage.setItem( sessionStorage.setItem(
key, key,
JSON.stringify({ JSON.stringify({
@ -356,7 +372,66 @@ limitations under the License. -->
configuration: JSON.stringify(c), configuration: JSON.stringify(c),
}; };
const res = await dashboardStore.updateDashboard(setting); const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) { if (res.data.changeTemplate.status) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
}),
);
}
}
}
items.push(d);
}
dashboardStore.resetDashboards(items);
searchDashboards(1);
loading.value = false;
}
async function handleTopLevel(row: DashboardItem) {
const items: DashboardItem[] = [];
loading.value = true;
for (const d of dashboardStore.dashboards) {
if (d.id === row.id) {
d.isDefault = !row.isDefault;
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
delete c.id;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.status) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
}),
);
}
} else {
if (d.layer === row.layer && [EntityType[0].value].includes(d.entity) && !row.isDefault && d.isDefault) {
d.isDefault = false;
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.status) {
sessionStorage.setItem( sessionStorage.setItem(
key, key,
JSON.stringify({ JSON.stringify({