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

    layer: drop crufty header tags + chrome, fix service-name plumbing
    
    Layer header card:
    - Drop the LAYER tag and 'OAP layer · level N' tag — operators don't
      need either; the layer dot + name already identifies the layer.
    - Restore layer-wide aggregate KPIs in the summary row (sum/avg per
      the operator's setup config). The selected-service context lives on
      the Switch button label below; KPIs themselves are the layer rollup
      again, as before.
    
    Service page (LayerDashboardsView):
    - Header chrome trimmed to just the service name + a 'refreshing /
      OAP unreachable' state pill. Drops the '7 widgets', the base64 id
      in 'MQE scoped to ...', and the 'Refreshes every 60s. Customize
      widgets.' blurb operators don't act on.
    - Service-name bug fix: the URL ?service= carries the base64 OAP id,
      but MQE entities are keyed by serviceName. View now looks the row
      up in the layer landing data (shared vue-query cache) and passes
      the actual serviceName to /api/layer/:key/dashboard. Widgets that
      previously came back empty should now render.
---
 apps/ui/src/views/layer/LayerDashboardsView.vue | 80 ++++++++++++-------------
 apps/ui/src/views/layer/LayerShell.vue          | 29 ++-------
 2 files changed, 43 insertions(+), 66 deletions(-)

diff --git a/apps/ui/src/views/layer/LayerDashboardsView.vue 
b/apps/ui/src/views/layer/LayerDashboardsView.vue
index 066c872..6372c45 100644
--- a/apps/ui/src/views/layer/LayerDashboardsView.vue
+++ b/apps/ui/src/views/layer/LayerDashboardsView.vue
@@ -27,20 +27,44 @@
 -->
 <script setup lang="ts">
 import { computed } from 'vue';
-import { useRoute, RouterLink } from 'vue-router';
+import { useRoute } from 'vue-router';
+import type { LayerDef } from '@skywalking-horizon-ui/api-client';
 import TimeChart from '@/components/charts/TimeChart.vue';
-import {
-  useLayerDashboard,
-  useLayerDashboardConfig,
-} from '@/composables/useLayerDashboard';
+import { useLayerDashboard, useLayerDashboardConfig } from 
'@/composables/useLayerDashboard';
+import { useLayerLanding } from '@/composables/useLayerLanding';
+import { useLayers } from '@/composables/useLayers';
 import { useSelectedService } from '@/composables/useSelectedService';
+import { useSetupStore } from '@/stores/setup';
 import { fmtMetric } from '@/utils/formatters';
 
 const route = useRoute();
 const layerKey = computed(() => String(route.params.layerKey ?? ''));
 const { selectedId } = useSelectedService();
+const { layers } = useLayers();
+const layer = computed<LayerDef | null>(() => layers.value.find((l) => l.key 
=== layerKey.value) ?? null);
+
+// Look up the service NAME from landing data — selectedId is the
+// base64 OAP service id, which MQE doesn't accept; MQE entities are
+// keyed by serviceName. We share the landing query with the rest of
+// the per-layer page so this is free (cached by vue-query).
+const store = useSetupStore();
+const safeLayer = computed<LayerDef>(() => layer.value ?? {
+  key: layerKey.value, name: layerKey.value, color: 'var(--sw-fg-2)',
+  serviceCount: -1, active: false, level: null, slots: {}, caps: {},
+});
+const safeCfg = computed(() => {
+  if (!layer.value) return { priority: 99, topN: 5, orderBy: 'cpm', columns: 
[], style: 'table' as const };
+  return store.ensure(layer.value.key, { slots: layer.value.slots, caps: 
layer.value.caps }).landing;
+});
+const landing = useLayerLanding(safeLayer, safeCfg);
+const serviceName = computed<string | null>(() => {
+  const rows = landing.data.value?.sampledRows ?? landing.rows.value ?? [];
+  const match = rows.find((r) => r.serviceId === selectedId.value);
+  return match?.serviceName ?? null;
+});
+
 const { config, isLoading: configLoading } = useLayerDashboardConfig(layerKey);
-const { data, isFetching, error } = useLayerDashboard(layerKey, selectedId);
+const { data, isFetching, error } = useLayerDashboard(layerKey, serviceName);
 
 const widgets = computed(() => config.value?.widgets ?? []);
 const resultsById = computed(() => {
@@ -48,26 +72,18 @@ const resultsById = computed(() => {
   for (const r of data.value?.widgets ?? []) out.set(r.id, r);
   return out;
 });
-const serviceText = computed(() => data.value?.service ?? selectedId.value ?? 
'(auto)');
 const reachable = computed(() => data.value?.reachable !== false);
 const errorText = computed(() => data.value?.error ?? (error.value ? 
String(error.value) : null));
+const headerTitle = computed(() => serviceName.value ?? data.value?.service ?? 
'Pick a service');
 </script>
 
 <template>
   <div class="dash-tab">
     <header class="dash-head">
-      <div>
-        <div class="kicker">Service</div>
-        <h2>{{ widgets.length }} widget{{ widgets.length === 1 ? '' : 's' 
}}</h2>
-        <p class="sub">
-          MQE scoped to <code>{{ serviceText }}</code>.
-          Refreshes every 60s. <RouterLink 
to="/admin/layer-dashboards">Customize widgets</RouterLink>.
-        </p>
-      </div>
+      <h2 class="svc-title">{{ headerTitle }}</h2>
       <div class="state">
         <span v-if="isFetching" class="badge fetch">refreshing</span>
         <span v-else-if="!reachable" class="badge err">OAP unreachable</span>
-        <span v-else class="badge ok">live</span>
       </div>
     </header>
 
@@ -130,38 +146,16 @@ const errorText = computed(() => data.value?.error ?? 
(error.value ? String(erro
 }
 .dash-head {
   display: flex;
-  align-items: flex-start;
+  align-items: center;
   gap: 12px;
 }
-.kicker {
-  font-size: 10px;
-  text-transform: uppercase;
-  letter-spacing: 0.1em;
-  color: var(--sw-accent);
-}
-.dash-head h2 {
-  margin: 2px 0 4px;
-  font-size: 15px;
+.svc-title {
+  margin: 0;
+  font-size: 14px;
   font-weight: 600;
   color: var(--sw-fg-0);
-  letter-spacing: -0.01em;
-}
-.dash-head .sub {
-  margin: 0;
-  font-size: 11px;
-  color: var(--sw-fg-2);
-  line-height: 1.5;
-}
-.dash-head .sub code {
   font-family: var(--sw-mono);
-  font-size: 10.5px;
-  background: var(--sw-bg-2);
-  padding: 1px 4px;
-  border-radius: 3px;
-}
-.dash-head .sub a {
-  color: var(--sw-accent-2);
-  text-decoration: none;
+  letter-spacing: -0.01em;
 }
 .state {
   margin-left: auto;
diff --git a/apps/ui/src/views/layer/LayerShell.vue 
b/apps/ui/src/views/layer/LayerShell.vue
index bdd7851..0c6cda0 100644
--- a/apps/ui/src/views/layer/LayerShell.vue
+++ b/apps/ui/src/views/layer/LayerShell.vue
@@ -129,12 +129,10 @@ interface HeaderKpi {
   spark?: Array<number | null>;
 }
 /**
- * Header KPIs scope to the *selected service*. Falls back to the
- * layer-wide aggregates when no service is selectable (e.g. before
- * data loads). The per-service sparkline comes from `row.spark` when
- * present; otherwise falls through to the layer-wide
- * `seriesByMetric[col.metric]` so the trend area never goes blank just
- * because the BFF only built one spark series.
+ * Header KPIs are layer-wide aggregates — sum or avg across the topN
+ * services per the operator's setup config. The Switch button below
+ * carries the *selected service* context for the widget grid; the
+ * header summary is the layer rollup.
  */
 const headerKpis = computed<HeaderKpi[]>(() => {
   const L = layer.value;
@@ -142,33 +140,20 @@ const headerKpis = computed<HeaderKpi[]>(() => {
   const c = cfg.value;
   if (!c) return [];
   const a = aggregates.value;
-  const row = selectedRow.value;
   const out: HeaderKpi[] = [];
   for (const col of c.landing.columns.slice(0, 5)) {
     const m = metricMeta(col.metric);
-    const value = row ? row.metrics[col.metric] ?? null : 
a?.metrics?.[col.metric] ?? null;
     out.push({
       label: col.label || m.label,
-      value,
+      value: a?.metrics?.[col.metric] ?? null,
       unit: col.unit || m.unit,
       color: colorForMetric(col.metric),
-      // Prefer the selected service's spark; otherwise reuse the
-      // layer-aggregate series.
-      spark:
-        row?.spark && row.spark.length > 1
-          ? row.spark
-          : a?.seriesByMetric?.[col.metric],
+      spark: a?.seriesByMetric?.[col.metric],
     });
   }
   return out;
 });
 
-// Source/level chip text (mirrors design's "from Java agent · OAP v10.3").
-const sourceText = computed(() => {
-  if (!layer.value) return '';
-  const lvl = layer.value.level;
-  return lvl !== null && lvl !== undefined ? `OAP layer · level ${lvl}` : 'OAP 
layer';
-});
 </script>
 
 <template>
@@ -180,8 +165,6 @@ const sourceText = computed(() => {
         <div class="identity-text">
           <div class="title-row">
             <h1>{{ displayName }}</h1>
-            <span class="sw-tag layer-tag">LAYER</span>
-            <span class="sw-tag">{{ sourceText }}</span>
             <span v-if="layer.serviceCount === 0" class="sw-badge warn">no 
services</span>
             <span v-else-if="!layer.active" class="sw-badge">no data</span>
           </div>

Reply via email to