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 4fbc2e8e72407e03f23f22c20c822800d2fcdc53
Author: Wu Sheng <[email protected]>
AuthorDate: Tue May 12 10:36:43 2026 +0800

    ui: add overview link plus marketplace; alarms top-level user-facing
---
 apps/ui/src/components/shell/AppSidebar.vue | 122 +++++++++++++++++++---------
 apps/ui/src/router/index.ts                 |  24 ++++--
 2 files changed, 100 insertions(+), 46 deletions(-)

diff --git a/apps/ui/src/components/shell/AppSidebar.vue 
b/apps/ui/src/components/shell/AppSidebar.vue
index 982db3e..7d09b33 100644
--- a/apps/ui/src/components/shell/AppSidebar.vue
+++ b/apps/ui/src/components/shell/AppSidebar.vue
@@ -43,20 +43,64 @@ interface NavRow {
   badge?: { text: string; kind?: 'ok' | 'warn' | 'err' | 'info' };
 }
 
-// Operate = OAP runtime operations (vantage-parity) + alarms.
-// Trace search is per-layer (lives under /layer/:key/traces) so it's NOT here.
-const operate: NavRow[] = [
-  { icon: 'alert', label: 'Alarms', to: '/operate/alarms', badge: { text: '7', 
kind: 'err' } },
-  { icon: 'svc', label: 'Cluster status', to: '/operate/cluster' },
-  { icon: 'set', label: 'DSL catalog', to: '/operate/dsl' },
-  { icon: 'metric', label: 'Inspect', to: '/operate/inspect' },
-  { icon: 'flame', label: 'Live debug', to: '/operate/live-debug' },
-  { icon: 'trace', label: 'OAL viewer', to: '/operate/oal' },
-  { icon: 'download', label: 'Dump', to: '/operate/dump' },
-];
-const admin: NavRow[] = [
-  { icon: 'user', label: 'Users', to: '/admin/users' },
-  { icon: 'set', label: 'Roles', to: '/admin/roles' },
+interface NavSection {
+  kicker: string;
+  links: NavRow[];
+}
+
+// One leading row before the Layers block — the cross-layer landing.
+const overview: NavRow = { icon: 'dash', label: 'Overview', to: '/' };
+
+// Vantage-style flat kickers for the Operate / Admin half of the sidebar.
+// Alarms is user-facing so it sits before the Operate block (between user
+// observability concerns and OAP operator concerns).
+const sections: NavSection[] = [
+  {
+    kicker: 'Alerts',
+    links: [{ icon: 'alert', label: 'Alarms', to: '/alarms', badge: { text: 
'7', kind: 'err' } }],
+  },
+  {
+    kicker: 'Marketplace',
+    links: [{ icon: 'metric', label: 'All dashboards', to: 
'/operate/marketplace' }],
+  },
+  {
+    kicker: 'Cluster',
+    links: [{ icon: 'svc', label: 'Cluster status', to: '/operate/cluster' }],
+  },
+  {
+    kicker: 'DSL Management',
+    links: [
+      { icon: 'set', label: 'MAL · OTEL', to: '/operate/dsl/otel-rules' },
+      { icon: 'set', label: 'MAL · Telegraf', to: 
'/operate/dsl/telegraf-rules' },
+      { icon: 'set', label: 'LAL', to: '/operate/dsl/lal' },
+      // log-mal-rules = MAL applied to LAL-derived logs; the data flow reads
+      // LAL → MAL so the label says so.
+      { icon: 'set', label: 'LAL → MAL', to: '/operate/dsl/log-mal-rules' },
+      { icon: 'trace', label: 'OAL · read-only', to: '/operate/oal' },
+    ],
+  },
+  {
+    kicker: 'Inspect',
+    links: [{ icon: 'metric', label: 'Inspect', to: '/operate/inspect' }],
+  },
+  {
+    kicker: 'Live debugger',
+    links: [
+      { icon: 'flame', label: 'Live debugger', to: '/operate/live-debug' },
+      { icon: 'event', label: 'Capture history', to: 
'/operate/live-debug/history' },
+    ],
+  },
+  {
+    kicker: 'Dump',
+    links: [{ icon: 'download', label: 'Dump & restore', to: '/operate/dump' 
}],
+  },
+  {
+    kicker: 'Admin',
+    links: [
+      { icon: 'user', label: 'Users', to: '/admin/users' },
+      { icon: 'set', label: 'Roles', to: '/admin/roles' },
+    ],
+  },
 ];
 </script>
 
@@ -68,6 +112,14 @@ const admin: NavRow[] = [
     </RouterLink>
 
     <nav class="sw-nav">
+      <RouterLink
+        :to="overview.to"
+        class="sw-nav-item lead"
+        :class="{ 'is-active': route.path === overview.to }"
+      >
+        <Icon :name="overview.icon" /><span>{{ overview.label }}</span>
+      </RouterLink>
+
       <div class="sw-nav-section sw-row" style="justify-content: 
space-between">
         <span>Layers</span>
         <span style="color: var(--sw-fg-3); font-weight: 400">{{ LAYERS.length 
}} layers</span>
@@ -172,30 +224,21 @@ const admin: NavRow[] = [
         </div>
       </template>
 
-      <div class="sw-nav-section">Operate</div>
-      <RouterLink
-        v-for="row in operate"
-        :key="row.to"
-        :to="row.to"
-        class="sw-nav-item"
-        :class="{ 'is-active': isActive(row.to) }"
-      >
-        <Icon :name="row.icon" /><span>{{ row.label }}</span>
-        <span v-if="row.badge" class="sw-badge" :class="row.badge.kind" 
style="margin-left: auto">
-          {{ row.badge.text }}
-        </span>
-      </RouterLink>
-
-      <div class="sw-nav-section">Admin</div>
-      <RouterLink
-        v-for="row in admin"
-        :key="row.to"
-        :to="row.to"
-        class="sw-nav-item"
-        :class="{ 'is-active': isActive(row.to) }"
-      >
-        <Icon :name="row.icon" /><span>{{ row.label }}</span>
-      </RouterLink>
+      <template v-for="sec in sections" :key="sec.kicker">
+        <div class="sw-nav-section">{{ sec.kicker }}</div>
+        <RouterLink
+          v-for="row in sec.links"
+          :key="row.to"
+          :to="row.to"
+          class="sw-nav-item"
+          :class="{ 'is-active': isActive(row.to) }"
+        >
+          <Icon :name="row.icon" /><span>{{ row.label }}</span>
+          <span v-if="row.badge" class="sw-badge" :class="row.badge.kind" 
style="margin-left: auto">
+            {{ row.badge.text }}
+          </span>
+        </RouterLink>
+      </template>
     </nav>
 
     <div class="sw-side-foot">
@@ -274,4 +317,7 @@ const admin: NavRow[] = [
 .sw-nav-item {
   text-decoration: none;
 }
+.sw-nav-item.lead {
+  margin-top: 4px;
+}
 </style>
diff --git a/apps/ui/src/router/index.ts b/apps/ui/src/router/index.ts
index d297819..cfdde91 100644
--- a/apps/ui/src/router/index.ts
+++ b/apps/ui/src/router/index.ts
@@ -85,16 +85,24 @@ function layerSubRoutes(): RouteRecordRaw[] {
 const shellRoutes: RouteRecordRaw[] = [
   { path: '', name: 'home', component: () => 
import('@/views/landing/LandingView.vue') },
   ...layerSubRoutes(),
-  // Operate (vantage-parity) — OAP runtime operations
-  { path: 'operate/alarms', component: placeholder, props: { title: 'Alarms', 
phase: 'Phase 5', note: 'Read-only; recovery is backend-auto. Live debug card 
via admin REST.' } },
+  // Alerts (user-facing — alarms are observability data, not operator-only)
+  { path: 'alarms', component: placeholder, props: { title: 'Alarms', phase: 
'Phase 5', note: 'Read-only; recovery is backend-auto. Live debug card via 
admin REST.' } },
+  // Marketplace — all dashboards / templates across layers
+  { path: 'operate/marketplace', component: placeholder, props: { title: 
'Marketplace', phase: 'Phase 2', note: 'All dashboard templates browse + clone 
+ customize.' } },
+  // Cluster
   { path: 'operate/cluster', component: placeholder, props: { title: 'Cluster 
status', phase: 'Phase 6 / 7', note: 'Module activity matrix · storage health · 
receiver activity · effective config tree · TTL grid.' } },
-  { path: 'operate/dsl', component: placeholder, props: { title: 'DSL 
catalog', phase: 'Phase 6', note: 'MAL / LAL rule catalog + Monaco editor with 
diff and revert-to-bundled.' } },
-  { path: 'operate/dsl/:catalog/:name', component: placeholder, props: (r) => 
({ title: `Edit · ${r.params.name}`, phase: 'Phase 6' }) },
+  // DSL Management
+  { path: 'operate/dsl/:catalog(otel-rules|telegraf-rules|lal|log-mal-rules)', 
component: placeholder, props: (r) => ({ title: `DSL · ${r.params.catalog}`, 
phase: 'Phase 6', note: 'Rule catalog grid + filter + new-rule form. Click a 
rule to open the editor.' }) },
+  { path: 
'operate/dsl/:catalog(otel-rules|telegraf-rules|lal|log-mal-rules)/:name', 
component: placeholder, props: (r) => ({ title: `Edit · ${r.params.name}`, 
phase: 'Phase 6', note: 'Monaco YAML + diff vs server + diff vs bundled + 
destructive-confirm.' }) },
+  { path: 'operate/oal', component: placeholder, props: { title: 'OAL · 
read-only', phase: 'Phase 6', note: 'Line-numbered OAL files with 
jump-to-debugger on each rule.' } },
+  // Inspect
   { path: 'operate/inspect', component: placeholder, props: { title: 
'Inspect', phase: 'Phase 6', note: 'OAP metric catalog browse + MQE ad-hoc 
charts with rule attribution.' } },
-  { path: 'operate/live-debug/:tab(mal|lal|oal)?', component: placeholder, 
props: (r) => ({ title: `Live debug · ${r.params.tab ?? 'mal'}`, phase: 'Phase 
6' }) },
-  { path: 'operate/oal', component: placeholder, props: { title: 'OAL viewer', 
phase: 'Phase 6', note: 'Read-only OAL files with line-numbered syntax 
highlighting.' } },
-  { path: 'operate/dump', component: placeholder, props: { title: 'Dump', 
phase: 'Phase 6', note: 'Stream OAP runtime-rule dump as tar.gz.' } },
-  // Admin (users + roles only; no audit log UI — BFF JSONL is server-side 
forensic only)
+  // Live debugger
+  { path: 'operate/live-debug/:tab(mal|lal|oal)?', component: placeholder, 
props: (r) => ({ title: `Live debugger · ${r.params.tab ?? 'mal'}`, phase: 
'Phase 6' }) },
+  { path: 'operate/live-debug/history', component: placeholder, props: { 
title: 'Capture history', phase: 'Phase 6', note: 'Local-only history of 
finished capture sessions.' } },
+  // Dump
+  { path: 'operate/dump', component: placeholder, props: { title: 'Dump & 
restore', phase: 'Phase 6', note: 'Stream OAP runtime-rule dump as tar.gz. 
Restore is deferred (no OAP endpoint yet).' } },
+  // Admin
   { path: 'admin/users', component: placeholder, props: { title: 'Users', 
phase: 'Phase 7' } },
   { path: 'admin/roles', component: placeholder, props: { title: 'Roles & 
permissions', phase: 'Phase 7' } },
 ];

Reply via email to