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 2026/05/17 03:27:45 UTC
(skywalking-horizon-ui) branch main updated (5f16170 -> 3c6f6c0)
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
from 5f16170 ui sidebar: group header click cascades to first layer's first tab
new 4e76a16 Revert "ui sidebar: group header click cascades to first layer's first tab"
new 3c6f6c0 Revert "ui sidebar: collapse duplicated layer-tab blocks + memoize nav per layer"
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
apps/ui/src/shell/AppSidebar.vue | 452 +++++++++++++++++++++++----------------
1 file changed, 266 insertions(+), 186 deletions(-)
(skywalking-horizon-ui) 01/02: Revert "ui sidebar: group header click cascades to first layer's first tab"
Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit 4e76a167a52a8ea67539c283a1f36f9f7e45f3c3
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun May 17 11:27:26 2026 +0800
Revert "ui sidebar: group header click cascades to first layer's first tab"
This reverts commit 5f161708d5ace2c42fcad578850689e8f5102554.
---
apps/ui/src/shell/AppSidebar.vue | 38 ++------------------------------------
1 file changed, 2 insertions(+), 36 deletions(-)
diff --git a/apps/ui/src/shell/AppSidebar.vue b/apps/ui/src/shell/AppSidebar.vue
index 8bd763b..94194ce 100644
--- a/apps/ui/src/shell/AppSidebar.vue
+++ b/apps/ui/src/shell/AppSidebar.vue
@@ -147,24 +147,6 @@ function toggleLayer(key: string): void {
}
}
-/**
- * Group-header click — cascade two levels: expand the first layer in
- * the group AND navigate to its first tab. Mirrors the behaviour
- * described as "for group menu, only the first sub menu select
- * (nested twice), then fire the real menu selection". Without this
- * the section labels were inert and the operator had to drill in via
- * two clicks (layer → tab) instead of one (group).
- */
-function pickGroupFirst(layers: SidebarLayer[]): void {
- const first = layers[0];
- if (!first) return;
- const nav = layerNavByKey.value.get(first.key);
- if (!nav) return;
- expandedLayer.value = first.key;
- if (route.path === nav.primaryTo) return;
- void router.push(nav.primaryTo);
-}
-
interface LayerGroup { kind: 'group'; label: string; layers: SidebarLayer[] }
interface LayerSingle { kind: 'single'; layer: SidebarLayer }
type SidebarEntry = LayerGroup | LayerSingle;
@@ -365,10 +347,7 @@ watch(
</div>
<template v-for="(E, ei) in sidebarEntries" :key="E.kind === 'group' ? `g:${E.label}` : `s:${E.layer.key}:${ei}`">
<template v-if="E.kind === 'group'">
- <div
- class="sw-nav-section sw-nav-section--icon sw-nav-section--clickable"
- @click="pickGroupFirst(E.layers)"
- >
+ <div class="sw-nav-section sw-nav-section--icon">
<Icon :name="sectionIcon(E.label)" />
<span class="layer-group-name">{{ E.label }}</span>
</div>
@@ -459,10 +438,7 @@ watch(
</template>
<template v-if="operateLayers.length > 0">
- <div
- class="sw-nav-section sw-nav-section--icon sw-nav-section--clickable"
- @click="pickGroupFirst(operateLayers)"
- >
+ <div class="sw-nav-section sw-nav-section--icon">
<Icon :name="sectionIcon('Platform monitoring')" />
<span>Platform monitoring</span>
</div>
@@ -782,16 +758,6 @@ watch(
color: var(--sw-fg-3);
opacity: 1;
}
-/* Clickable section header — cascades through the first child layer
- * to its first tab. Hover lifts the text so the affordance reads. */
-.sw-nav-section--clickable {
- cursor: pointer;
- user-select: none;
-}
-.sw-nav-section--clickable:hover,
-.sw-nav-section--clickable:hover :deep(svg) {
- color: var(--sw-fg-1);
-}
.layer-group-name { flex: 1; min-width: 0; }
/* Grouped and ungrouped layer rows sit at the same indent — the group
* header already delineates the section, so no extra tree-style nest. */
(skywalking-horizon-ui) 02/02: Revert "ui sidebar: collapse duplicated layer-tab blocks + memoize nav per layer"
Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit 3c6f6c06a9bb9ff4e27f66208b008239ddef59ef
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun May 17 11:27:26 2026 +0800
Revert "ui sidebar: collapse duplicated layer-tab blocks + memoize nav per layer"
This reverts commit c3544de5ae3e1d2c5fd0fd435b66346724955990.
---
apps/ui/src/shell/AppSidebar.vue | 414 +++++++++++++++++++++++++--------------
1 file changed, 264 insertions(+), 150 deletions(-)
diff --git a/apps/ui/src/shell/AppSidebar.vue b/apps/ui/src/shell/AppSidebar.vue
index 94194ce..b3661dd 100644
--- a/apps/ui/src/shell/AppSidebar.vue
+++ b/apps/ui/src/shell/AppSidebar.vue
@@ -20,7 +20,7 @@ import { RouterLink, useRoute, useRouter } from 'vue-router';
import Icon, { type IconName } from '@/components/icons/Icon.vue';
import logoSw from '@/assets/icons/logo-sw.svg?raw';
import { useAuthStore } from '@/state/auth';
-import { useLayers } from '@/shell/useLayers';
+import { useLayers, firstLayerTab } from '@/shell/useLayers';
import { useLandingOrder } from '@/shell/useLandingOrder';
import { useOverviewDashboards } from '@/render/overview/useOverviewDashboards';
@@ -31,7 +31,7 @@ async function signOut(): Promise<void> {
await router.push({ name: 'login' });
}
-const { availableLayers, oapReachable, oapError } = useLayers();
+const { availableLayers, oapReachable, oapError, hasTopology } = useLayers();
const orderedLayers = useLandingOrder(availableLayers);
const { publicOverviews } = useOverviewDashboards();
@@ -41,97 +41,21 @@ function layerIcon(L: SidebarLayer): IconName {
}
type SidebarLayer = (typeof orderedLayers.value)[number];
-
-/**
- * Per-layer navigation descriptor. Built ONCE per layer set (rebuilt
- * only when `orderedLayers` changes) and cached in `layerNavByKey`.
- * Lets the template render each tab via a single `v-for` instead of
- * the dozen inline `v-if cap` blocks that fired on every route change
- * and made the sidebar feel sluggish on OAPs with many layers.
- *
- * `isSingle` — the layer collapses to a direct link (no accordion).
- * `primaryTo` — `/layer/<key>/<firstLayerTab>` (used by the row click).
- * `tabs` — child rows; empty when `isSingle`.
- */
-interface LayerTab {
- key: string;
- icon: IconName;
- label: string;
- to: string;
- /** Optional badge — currently only Service shows the service count. */
- badge?: number;
+function hasInstances(L: SidebarLayer): boolean {
+ return L.caps.instances ?? Boolean(L.slots.instances);
}
-interface LayerNav {
- isSingle: boolean;
- primaryTo: string;
- tabs: LayerTab[];
+function hasEndpoints(L: SidebarLayer): boolean {
+ return L.caps.endpoints ?? Boolean(L.slots.endpoints);
}
-
-function buildLayerNav(L: SidebarLayer): LayerNav {
+/** A layer whose only worthwhile screen is the services list — no
+ * tabs to expand into. Rendered as a direct link, not an accordion. */
+function isSingleFeatureLayer(L: SidebarLayer): boolean {
+ if (hasInstances(L) || hasEndpoints(L)) return false;
+ if (hasTopology(L)) return false;
const c = L.caps;
- const slots = L.slots;
- const hasInstances = c.instances ?? Boolean(slots.instances);
- const hasEndpoints = c.endpoints ?? Boolean(slots.endpoints);
- const hasTopo = Boolean(c.serviceMap || c.instanceTopology || c.processTopology);
- const hasAny =
- hasInstances ||
- hasEndpoints ||
- hasTopo ||
- Boolean(
- c.traces ||
- c.logs ||
- c.traceProfiling ||
- c.ebpfProfiling ||
- c.asyncProfiling ||
- c.networkProfiling ||
- c.pprofProfiling ||
- c.events ||
- c.endpointDependency,
- );
- const firstTab = (() => {
- if (c.dashboards) return 'service';
- if (hasInstances) return 'instance';
- if (hasEndpoints) return 'endpoint';
- if (hasTopo) return 'topology';
- if (c.endpointDependency) return 'dependency';
- if (c.traces) return 'trace';
- if (c.logs) return 'logs';
- if (c.traceProfiling) return 'trace-profiling';
- if (c.ebpfProfiling) return 'ebpf-profiling';
- if (c.networkProfiling) return 'network-profiling';
- if (c.asyncProfiling) return 'async-profiling';
- if (c.pprofProfiling) return 'pprof';
- return 'service';
- })();
- const primaryTo = `/layer/${L.key}/${firstTab}`;
- const isSingle = !hasAny;
- const tabs: LayerTab[] = [];
- const push = (t: LayerTab) => tabs.push(t);
- if (c.dashboards) push({ key: 'service', icon: 'svc', label: 'Service', to: `/layer/${L.key}/service`, badge: L.serviceCount });
- if (hasInstances) push({ key: 'instance', icon: 'prof', label: slots.instances ?? 'Instance', to: `/layer/${L.key}/instance` });
- if (hasEndpoints) push({ key: 'endpoint', icon: 'ep', label: slots.endpoints ?? 'Endpoint', to: `/layer/${L.key}/endpoint` });
- if (hasTopo) push({ key: 'topology', icon: 'topo', label: 'Topology', to: `/layer/${L.key}/topology` });
- if (c.endpointDependency) {
- const label = slots.endpointDependency ?? `${slots.endpoints ?? 'Endpoint'} dependency`;
- push({ key: 'dependency', icon: 'ep', label, to: `/layer/${L.key}/dependency` });
- }
- if (c.traces) push({ key: 'trace', icon: 'trace', label: 'Traces', to: `/layer/${L.key}/trace` });
- if (c.logs) push({ key: 'logs', icon: 'log', label: 'Logs', to: `/layer/${L.key}/logs` });
- if (c.traceProfiling) push({ key: 'trace-profiling', icon: 'flame', label: 'Trace Profiling', to: `/layer/${L.key}/trace-profiling` });
- if (c.ebpfProfiling) push({ key: 'ebpf-profiling', icon: 'flame', label: 'eBPF Profiling', to: `/layer/${L.key}/ebpf-profiling` });
- if (c.networkProfiling) push({ key: 'network-profiling', icon: 'prof', label: 'Network Profiling', to: `/layer/${L.key}/network-profiling` });
- if (c.pprofProfiling) push({ key: 'pprof', icon: 'prof', label: 'pprof (Go)', to: `/layer/${L.key}/pprof` });
- if (c.asyncProfiling) push({ key: 'async-profiling', icon: 'flame', label: 'Async Profiling', to: `/layer/${L.key}/async-profiling` });
- return { isSingle, primaryTo, tabs };
-}
-
-const layerNavByKey = computed<Map<string, LayerNav>>(() => {
- const m = new Map<string, LayerNav>();
- for (const L of orderedLayers.value) m.set(L.key, buildLayerNav(L));
- return m;
-});
-function navFor(L: SidebarLayer): LayerNav {
- return layerNavByKey.value.get(L.key) ?? buildLayerNav(L);
+ if (c.traces || c.logs || c.traceProfiling || c.ebpfProfiling || c.asyncProfiling || c.events) return false;
+ if (c.endpointDependency || c.serviceMap || c.instanceTopology || c.processTopology) return false;
+ return true;
}
const expandedLayer = ref<string | null>(null);
@@ -139,11 +63,12 @@ function toggleLayer(key: string): void {
const wasExpanded = expandedLayer.value === key;
expandedLayer.value = wasExpanded ? null : key;
if (!wasExpanded) {
- const nav = layerNavByKey.value.get(key);
- if (!nav) return;
- if (route.path === nav.primaryTo) return;
- if (route.path.startsWith(`/layer/${key}/`)) return;
- void router.push(nav.primaryTo);
+ const L = orderedLayers.value.find((l) => l.key === key);
+ if (!L) return;
+ const target = `/layer/${L.key}/${firstLayerTab(L)}`;
+ if (route.path === target) return;
+ if (route.path.startsWith(`/layer/${L.key}/`)) return;
+ void router.push(target);
}
}
@@ -352,52 +277,139 @@ watch(
<span class="layer-group-name">{{ E.label }}</span>
</div>
<template v-for="L in E.layers" :key="`${E.label}::${L.key}`">
- <RouterLink
- v-if="navFor(L).isSingle"
- :to="navFor(L).primaryTo"
- class="layer-row direct in-group"
- :class="{ 'is-active': isActive(`/layer/${L.key}`) }"
- >
- <Icon :name="layerIcon(L)" />
- <span class="layer-name">{{ L.name }}</span>
- </RouterLink>
- <div
- v-else
- class="layer-row in-group"
- :class="{
- 'is-expanded': expandedLayer === L.key,
- 'is-active': isActiveExact(`/layer/${L.key}`),
- }"
- @click="toggleLayer(L.key)"
- >
- <Icon :name="layerIcon(L)" />
- <span class="layer-name">{{ L.name }}</span>
- <span class="caret" :class="{ open: expandedLayer === L.key }">
- <Icon name="caret" :size="10" />
- </span>
- </div>
- <div
- v-if="!navFor(L).isSingle && expandedLayer === L.key"
- class="layer-children in-group"
- >
<RouterLink
- v-for="tab in navFor(L).tabs"
- :key="tab.key"
- :to="tab.to"
- class="sw-nav-item"
- :class="{ 'is-active': isActive(tab.to) }"
+ v-if="isSingleFeatureLayer(L)"
+ :to="`/layer/${L.key}/${firstLayerTab(L)}`"
+ class="layer-row direct in-group"
+ :class="{ 'is-active': isActive(`/layer/${L.key}`) }"
>
- <Icon :name="tab.icon" /><span>{{ tab.label }}</span>
- <span v-if="tab.badge != null" class="sw-badge" style="margin-left: auto">{{ tab.badge }}</span>
+ <Icon :name="layerIcon(L)" />
+ <span class="layer-name">{{ L.name }}</span>
</RouterLink>
- </div>
- </template>
+ <div
+ v-else
+ class="layer-row in-group"
+ :class="{
+ 'is-expanded': expandedLayer === L.key,
+ 'is-active': isActiveExact(`/layer/${L.key}`),
+ }"
+ @click="toggleLayer(L.key)"
+ >
+ <Icon :name="layerIcon(L)" />
+ <span class="layer-name">{{ L.name }}</span>
+ <span class="caret" :class="{ open: expandedLayer === L.key }">
+ <Icon name="caret" :size="10" />
+ </span>
+ </div>
+ <div
+ v-if="!isSingleFeatureLayer(L) && expandedLayer === L.key"
+ class="layer-children in-group"
+ >
+ <RouterLink
+ v-if="L.caps.dashboards"
+ :to="`/layer/${L.key}/${firstLayerTab(L)}`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/${firstLayerTab(L)}`) || route.path === `/layer/${L.key}` }"
+ >
+ <Icon name="svc" /><span>Service</span>
+ <span class="sw-badge" style="margin-left: auto">{{ L.serviceCount }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasInstances(L)"
+ :to="`/layer/${L.key}/instance`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/instance`) }"
+ >
+ <Icon name="prof" /><span>{{ L.slots.instances ?? 'Instance' }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasEndpoints(L)"
+ :to="`/layer/${L.key}/endpoint`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/endpoint`) }"
+ >
+ <Icon name="ep" /><span>{{ L.slots.endpoints ?? 'Endpoint' }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasTopology(L)"
+ :to="`/layer/${L.key}/topology`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/topology`) }"
+ >
+ <Icon name="topo" /><span>Topology</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.endpointDependency"
+ :to="`/layer/${L.key}/dependency`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/dependency`) }"
+ >
+ <Icon name="ep" /><span>{{ L.slots.endpointDependency || 'Dependency' }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.traces"
+ :to="`/layer/${L.key}/trace`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/trace`) }"
+ >
+ <Icon name="trace" /><span>Trace</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.logs"
+ :to="`/layer/${L.key}/logs`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/logs`) }"
+ >
+ <Icon name="log" /><span>Logs</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.traceProfiling"
+ :to="`/layer/${L.key}/trace-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/trace-profiling`) }"
+ >
+ <Icon name="prof" /><span>Trace Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.ebpfProfiling"
+ :to="`/layer/${L.key}/ebpf-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/ebpf-profiling`) }"
+ >
+ <Icon name="prof" /><span>eBPF Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.asyncProfiling"
+ :to="`/layer/${L.key}/async-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/async-profiling`) }"
+ >
+ <Icon name="prof" /><span>Async Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.networkProfiling"
+ :to="`/layer/${L.key}/network-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/network-profiling`) }"
+ >
+ <Icon name="prof" /><span>Network Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="L.caps.pprofProfiling"
+ :to="`/layer/${L.key}/pprof`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/pprof`) }"
+ >
+ <Icon name="prof" /><span>pprof (Go)</span>
+ </RouterLink>
+ </div>
+ </template>
</template>
<!-- Ungrouped single-feature layer: direct link. -->
<RouterLink
- v-else-if="navFor(E.layer).isSingle"
- :to="navFor(E.layer).primaryTo"
+ v-else-if="isSingleFeatureLayer(E.layer)"
+ :to="`/layer/${E.layer.key}/${firstLayerTab(E.layer)}`"
class="layer-row direct"
:class="{ 'is-active': isActive(`/layer/${E.layer.key}`) }"
>
@@ -421,18 +433,105 @@ watch(
</span>
</div>
<div
- v-if="E.kind === 'single' && !navFor(E.layer).isSingle && expandedLayer === E.layer.key"
+ v-if="E.kind === 'single' && !isSingleFeatureLayer(E.layer) && expandedLayer === E.layer.key"
class="layer-children"
>
<RouterLink
- v-for="tab in navFor(E.layer).tabs"
- :key="tab.key"
- :to="tab.to"
+ v-if="E.layer.caps.dashboards"
+ :to="`/layer/${E.layer.key}/${firstLayerTab(E.layer)}`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/${firstLayerTab(E.layer)}`) || route.path === `/layer/${E.layer.key}` }"
+ >
+ <Icon name="svc" /><span>Service</span>
+ <span class="sw-badge" style="margin-left: auto">{{ E.layer.serviceCount }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasInstances(E.layer)"
+ :to="`/layer/${E.layer.key}/instance`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/instance`) }"
+ >
+ <Icon name="prof" /><span>{{ E.layer.slots.instances ?? 'Instance' }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasEndpoints(E.layer)"
+ :to="`/layer/${E.layer.key}/endpoint`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/endpoint`) }"
+ >
+ <Icon name="ep" /><span>{{ E.layer.slots.endpoints ?? 'Endpoint' }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasTopology(E.layer)"
+ :to="`/layer/${E.layer.key}/topology`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/topology`) }"
+ >
+ <Icon name="topo" /><span>Topology</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.endpointDependency"
+ :to="`/layer/${E.layer.key}/dependency`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/dependency`) }"
+ >
+ <Icon name="ep" /><span>{{ E.layer.slots.endpointDependency ?? `${E.layer.slots.endpoints ?? 'Endpoint'} dependency` }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.traces"
+ :to="`/layer/${E.layer.key}/trace`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/trace`) }"
+ >
+ <Icon name="trace" /><span>Traces</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.logs"
+ :to="`/layer/${E.layer.key}/logs`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/logs`) }"
+ >
+ <Icon name="log" /><span>Logs</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.traceProfiling"
+ :to="`/layer/${E.layer.key}/trace-profiling`"
class="sw-nav-item"
- :class="{ 'is-active': isActive(tab.to) }"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/trace-profiling`) }"
>
- <Icon :name="tab.icon" /><span>{{ tab.label }}</span>
- <span v-if="tab.badge != null" class="sw-badge" style="margin-left: auto">{{ tab.badge }}</span>
+ <Icon name="flame" /><span>Trace Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.ebpfProfiling"
+ :to="`/layer/${E.layer.key}/ebpf-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/ebpf-profiling`) }"
+ >
+ <Icon name="flame" /><span>eBPF Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.networkProfiling"
+ :to="`/layer/${E.layer.key}/network-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/network-profiling`) }"
+ >
+ <Icon name="prof" /><span>Network Profiling</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.pprofProfiling"
+ :to="`/layer/${E.layer.key}/pprof`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/pprof`) }"
+ >
+ <Icon name="prof" /><span>pprof (Go)</span>
+ </RouterLink>
+ <RouterLink
+ v-if="E.layer.caps.asyncProfiling"
+ :to="`/layer/${E.layer.key}/async-profiling`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${E.layer.key}/async-profiling`) }"
+ >
+ <Icon name="flame" /><span>Async Profiling</span>
</RouterLink>
</div>
</template>
@@ -444,8 +543,8 @@ watch(
</div>
<template v-for="L in operateLayers" :key="`op:${L.key}`">
<RouterLink
- v-if="navFor(L).isSingle"
- :to="navFor(L).primaryTo"
+ v-if="isSingleFeatureLayer(L)"
+ :to="`/layer/${L.key}/${firstLayerTab(L)}`"
class="layer-row direct"
:class="{ 'is-active': isActive(`/layer/${L.key}`) }"
>
@@ -469,18 +568,33 @@ watch(
</span>
</div>
<div
- v-if="!navFor(L).isSingle && expandedLayer === L.key"
+ v-if="!isSingleFeatureLayer(L) && expandedLayer === L.key"
class="layer-children"
>
<RouterLink
- v-for="tab in navFor(L).tabs"
- :key="tab.key"
- :to="tab.to"
+ v-if="L.caps.dashboards"
+ :to="`/layer/${L.key}/${firstLayerTab(L)}`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/${firstLayerTab(L)}`) || route.path === `/layer/${L.key}` }"
+ >
+ <Icon name="svc" /><span>Service</span>
+ <span class="sw-badge" style="margin-left: auto">{{ L.serviceCount }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasInstances(L)"
+ :to="`/layer/${L.key}/instance`"
+ class="sw-nav-item"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/instance`) }"
+ >
+ <Icon name="prof" /><span>{{ L.slots.instances ?? 'Instance' }}</span>
+ </RouterLink>
+ <RouterLink
+ v-if="hasEndpoints(L)"
+ :to="`/layer/${L.key}/endpoint`"
class="sw-nav-item"
- :class="{ 'is-active': isActive(tab.to) }"
+ :class="{ 'is-active': isActive(`/layer/${L.key}/endpoint`) }"
>
- <Icon :name="tab.icon" /><span>{{ tab.label }}</span>
- <span v-if="tab.badge != null" class="sw-badge" style="margin-left: auto">{{ tab.badge }}</span>
+ <Icon name="ep" /><span>{{ L.slots.endpoints ?? 'Endpoint' }}</span>
</RouterLink>
</div>
</template>