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 c1cf0deb86bba2b31fe719ccf8f4892cebbddb4e
Author: Wu Sheng <[email protected]>
AuthorDate: Tue May 12 23:01:43 2026 +0800

    admin: scope per component — add dependency / topology / logs tabs
    
    DashboardScope union expanded from 5 → 8 entries, matching the
    component-toggle list 1:1:
    
      service, instance, endpoint, dependency, topology, trace, logs, profiling
    
    LayerDashboards type + BFF zod schema (route body + admin save) +
    admin SCOPES const all picked up the three new keys. The scope tab
    strip in /admin/layer-dashboards now renders one tab per *enabled*
    component — when an operator toggles a component off, its scope tab
    disappears from the editor and the active scope snaps to the first
    remaining visible scope.
    
    Layers without those components see fewer tabs; layers with all 8
    on (general) see every editable widget grid. Each new scope has its
    own dashboards.<scope> array — operators can populate widgets for
    the dependency / topology / logs scopes whenever the per-page UI
    matures.
---
 apps/bff/src/dashboard/routes.ts                 | 14 +++++++-
 apps/bff/src/layers/loader.ts                    |  3 ++
 apps/ui/src/views/admin/LayerDashboardsAdmin.vue | 41 ++++++++++++++++++++++--
 packages/api-client/src/dashboard.ts             | 15 ++++++++-
 4 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/apps/bff/src/dashboard/routes.ts b/apps/bff/src/dashboard/routes.ts
index 778b5c9..cc812e1 100644
--- a/apps/bff/src/dashboard/routes.ts
+++ b/apps/bff/src/dashboard/routes.ts
@@ -78,7 +78,16 @@ const widgetSchema = z.object({
   w: z.number().int().positive().optional(),
   h: z.number().int().positive().optional(),
 });
-const scopeSchema = z.enum(['service', 'instance', 'endpoint', 'trace', 
'profiling']);
+const scopeSchema = z.enum([
+  'service',
+  'instance',
+  'endpoint',
+  'dependency',
+  'topology',
+  'trace',
+  'logs',
+  'profiling',
+]);
 const bodySchema = z.object({
   service: z.string().optional(),
   widgets: z.array(widgetSchema).max(40).optional(),
@@ -558,7 +567,10 @@ export function registerDashboardRoute(app: 
FastifyInstance, deps: DashboardRout
         service: z.array(widgetSchema).max(40).optional(),
         instance: z.array(widgetSchema).max(40).optional(),
         endpoint: z.array(widgetSchema).max(40).optional(),
+        dependency: z.array(widgetSchema).max(40).optional(),
+        topology: z.array(widgetSchema).max(40).optional(),
         trace: z.array(widgetSchema).max(40).optional(),
+        logs: z.array(widgetSchema).max(40).optional(),
         profiling: z.array(widgetSchema).max(40).optional(),
       })
       .strict()
diff --git a/apps/bff/src/layers/loader.ts b/apps/bff/src/layers/loader.ts
index 4a8b93d..e6a9c5d 100644
--- a/apps/bff/src/layers/loader.ts
+++ b/apps/bff/src/layers/loader.ts
@@ -95,7 +95,10 @@ export interface LayerDashboards {
   service?: DashboardWidget[];
   instance?: DashboardWidget[];
   endpoint?: DashboardWidget[];
+  dependency?: DashboardWidget[];
+  topology?: DashboardWidget[];
   trace?: DashboardWidget[];
+  logs?: DashboardWidget[];
   profiling?: DashboardWidget[];
 }
 
diff --git a/apps/ui/src/views/admin/LayerDashboardsAdmin.vue 
b/apps/ui/src/views/admin/LayerDashboardsAdmin.vue
index 34f3860..5829877 100644
--- a/apps/ui/src/views/admin/LayerDashboardsAdmin.vue
+++ b/apps/ui/src/views/admin/LayerDashboardsAdmin.vue
@@ -32,7 +32,16 @@ import type { AdminLayerTemplate } from '@/api/client';
 import type { DashboardScope, DashboardWidget } from 
'@skywalking-horizon-ui/api-client';
 import { bffClient } from '@/api/client';
 
-const SCOPES: DashboardScope[] = ['service', 'instance', 'endpoint', 'trace', 
'profiling'];
+const SCOPES: DashboardScope[] = [
+  'service',
+  'instance',
+  'endpoint',
+  'dependency',
+  'topology',
+  'trace',
+  'logs',
+  'profiling',
+];
 
 const templates = ref<AdminLayerTemplate[]>([]);
 const isLoading = ref(true);
@@ -72,6 +81,34 @@ function syncDraft(): void {
 watch(selectedKey, syncDraft);
 onMounted(loadAll);
 
+/**
+ * Map each DashboardScope to its corresponding `components.*` flag.
+ * Used to filter the scope tab strip so admin only surfaces tabs for
+ * components the operator has toggled on.
+ */
+const SCOPE_COMPONENT: Record<DashboardScope, ComponentKey> = {
+  service: 'service',
+  instance: 'instances',
+  endpoint: 'endpoints',
+  dependency: 'endpointDependency',
+  topology: 'topology',
+  trace: 'traces',
+  logs: 'logs',
+  profiling: 'profiling',
+};
+const visibleScopes = computed<DashboardScope[]>(() => {
+  const tpl = draft.template;
+  if (!tpl?.components) return SCOPES;
+  return SCOPES.filter((s) => tpl.components[SCOPE_COMPONENT[s]]);
+});
+watch(visibleScopes, (scopes) => {
+  // If the currently-active scope was just toggled off, snap to the
+  // first remaining visible scope so the editor stays on solid ground.
+  if (!scopes.includes(activeScope.value)) {
+    activeScope.value = scopes[0] ?? 'service';
+  }
+});
+
 const dirty = computed(() => {
   const original = templates.value.find((t) => t.key === selectedKey.value);
   if (!original || !draft.template) return false;
@@ -429,7 +466,7 @@ function toggleComponent(key: ComponentKey): void {
         <!-- Scope tabs -->
         <nav class="scope-tabs sw-card">
           <button
-            v-for="s in SCOPES"
+            v-for="s in visibleScopes"
             :key="s"
             class="scope-tab"
             :class="{ on: activeScope === s }"
diff --git a/packages/api-client/src/dashboard.ts 
b/packages/api-client/src/dashboard.ts
index 5c49311..57d5d5e 100644
--- a/packages/api-client/src/dashboard.ts
+++ b/packages/api-client/src/dashboard.ts
@@ -42,7 +42,20 @@ export type DashboardWidgetType = 'card' | 'line' | 'top';
  *   `trace`     = trace explorer for the selected entity
  *   `profiling` = flame graphs / sampled stacks
  */
-export type DashboardScope = 'service' | 'instance' | 'endpoint' | 'trace' | 
'profiling';
+/**
+ * One scope per component on a layer. Each scope owns its own widget
+ * grid (`dashboards.<scope>` array). The set mirrors the layer's
+ * component toggles 1:1 — every enabled component is configurable.
+ */
+export type DashboardScope =
+  | 'service'
+  | 'instance'
+  | 'endpoint'
+  | 'dependency'
+  | 'topology'
+  | 'trace'
+  | 'logs'
+  | 'profiling';
 
 export interface DashboardWidget {
   /** Stable id within the layer's dashboard. */

Reply via email to