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


The following commit(s) were added to refs/heads/main by this push:
     new f4cc470  fix: pre-1.0 audit follow-ups — changelog consolidation, RBAC 
UI parity, 500 error-leak (#84)
f4cc470 is described below

commit f4cc470b90911c360168ac1bea7ec82d64b18dd9
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Sun Jun 28 11:02:06 2026 +0800

    fix: pre-1.0 audit follow-ups — changelog consolidation, RBAC UI parity, 
500 error-leak (#84)
---
 CHANGELOG.md              |  9 ++-------
 apps/bff/src/server.ts    | 14 +++++++++++---
 apps/ui/src/state/auth.ts | 15 ++++++++++-----
 3 files changed, 23 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18ee27e..0398ddb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@ Notable changes to Apache SkyWalking Horizon UI, written from 
the operator's poi
 
 The version line is shared by every package in the monorepo (apps + shared 
packages) plus the BFF's `HORIZON_VERSION` default.
 
-## 1.1.0
+## 1.0.0
 
 ### Deployment & configuration
 
@@ -24,10 +24,6 @@ The version line is shared by every package in the monorepo 
(apps + shared packa
 
 - **pprof and async-profiling tasks open a detail modal with their captured 
logs.**
 
-### Logs
-
-- **Log and browser-error lists query on demand, not on every edit.** The 
per-layer Logs tab, cross-layer Log inspect, and the Browser Errors tab now 
stage condition changes and fetch only when you press **Run query** — a fresh 
tab shows a "Pick your conditions, then click Run query" prompt, and switching 
service resets to that prompt (clearing the level / tag / category filters), so 
the previous service's data never lingers under the new one.
-
 ### Alarms
 
 - **The alarm timeline reads more clearly** — a clearer selection band and 
legend, and the detail sidebar reflows cleanly on narrow windows. Hovering the 
timeline now hints both affordances — click a minute to filter, or drag across 
the timeline to select a range — so range-selection is no longer hidden.
@@ -48,8 +44,6 @@ The version line is shared by every package in the monorepo 
(apps + shared packa
 
 - **The Kubernetes Node dashboard gains a Pod Total card.** A compact card now 
sits directly under Node Status showing the current count of pods scheduled on 
the selected node (all phases) — the latest value of the same metric the "Pods 
on Node" trend already charts — so the space beside the status card is no 
longer blank.
 
-## 1.0.0
-
 ### Performance & behavior tuning
 
 - **New `performance` section in `horizon.yaml`.** Tune how hard the BFF fans 
metric queries out to OAP — per-route bulk (request) sizes and concurrency for 
the topology, 3D-map, landing, and dashboard fan-outs — plus protective caps: 
the service-map render valve (`topologyMaxNodes` / `topologyMaxEdges`) and 
per-request record caps for traces / logs / browser logs. Operational, 
hot-reloaded, per-deployment; defaults match the previous built-in values, so 
the whole block is optional. Rais [...]
@@ -65,6 +59,7 @@ The version line is shared by every package in the monorepo 
(apps + shared packa
 
 ### Logs
 
+- **Log and browser-error lists query on demand, not on every edit.** The 
per-layer Logs tab, cross-layer Log inspect, and the Browser Errors tab now 
stage condition changes and fetch only when you press **Run query** — a fresh 
tab shows a "Pick your conditions, then click Run query" prompt, and switching 
service resets to that prompt (clearing the level / tag / category filters), so 
the previous service's data never lingers under the new one.
 - **Log inspect uses the full width.** The cross-layer Log inspect form 
(Target + Tags / Trace ID / Time / Limit conditions) now spans the whole page 
instead of sharing a two-column strip with empty space.
 - **Clicking a log row opens a centered popout.** Both the cross-layer Log 
inspect and the per-layer Logs tab now open the same full-payload popout on row 
click — format-aware pretty-print (JSON pretty-printed by content type), the 
tags table, service / instance / endpoint / time meta, a copy button, and the 
trace link. Escape or the close button dismisses it.
 - **Log inspect can now query Browser errors across the page.** A new 
**Browser** source on Log inspect (beside Raw) queries the BROWSER layer's JS 
error logs from anywhere — pick a browser service or type a service name (or 
leave it blank for all services), then narrow by category (AJAX / RESOURCE / 
VUE / PROMISE / JS / UNKNOWN), version, page, and time window, and read the 
error list (message, category, page path, app version, time, minified 
`line:col`). Upload and manage source maps i [...]
diff --git a/apps/bff/src/server.ts b/apps/bff/src/server.ts
index eba8ce9..beea85f 100644
--- a/apps/bff/src/server.ts
+++ b/apps/bff/src/server.ts
@@ -151,13 +151,21 @@ source.onChange((cfg) => {
 
 const app = Fastify({ logger: loggerOptions });
 
-app.setErrorHandler((err, _req, reply) => {
+app.setErrorHandler((err, req, reply) => {
   if (err instanceof HttpError) {
     return reply.status(err.statusCode).send({ code: err.code, message: 
err.message, details: err.details });
   }
-  const message = err instanceof Error ? err.message : 'internal error';
+  // Never leak an internal / upstream exception message to the client — it can
+  // carry upstream response snippets or endpoint details. Log it server-side,
+  // return a generic body plus the request id for correlation; dev keeps the
+  // raw message for debugging.
   reply.log.error({ err }, 'unhandled');
-  return reply.status(500).send({ code: 'internal_error', message });
+  const isDev = process.env.NODE_ENV === 'development';
+  return reply.status(500).send({
+    code: 'internal_error',
+    message: isDev && err instanceof Error ? err.message : 'internal error',
+    requestId: req.id,
+  });
 });
 
 // Baseline security headers on every response (MIME-sniff / clickjacking /
diff --git a/apps/ui/src/state/auth.ts b/apps/ui/src/state/auth.ts
index 3d5f781..161feb5 100644
--- a/apps/ui/src/state/auth.ts
+++ b/apps/ui/src/state/auth.ts
@@ -77,14 +77,19 @@ export const useAuthStore = defineStore('auth', () => {
     user.value = null;
   }
 
+  // Mirrors the BFF's matchOne (apps/bff/src/rbac/verbs.ts) exactly. This UI 
gate
+  // is advisory — the BFF enforces — but it must agree, or it hides custom 
`admin`
+  // grants and shows three-segment controls (e.g. rule:write:structural) that 
a
+  // two-segment grant like `*:write` does not actually carry and the BFF 
denies.
   function hasVerb(verb: string): boolean {
     const grants = user.value?.verbs ?? [];
     for (const g of grants) {
-      if (g === '*' || g === verb) return true;
-      const [ga, gact] = g.split(':', 2);
-      const [ra, ract] = verb.split(':', 2);
-      if (gact === '*' && ga === ra) return true;
-      if (ga === '*' && gact === ract) return true;
+      if (g === '*' || g === 'admin' || g === verb) return true;
+      const [ga, gact, gsub] = g.split(':', 3);
+      const [ra, ract, rsub] = verb.split(':', 3);
+      if (ga === ra && gact === '*') return true;
+      if (ga === '*' && gact === ract && (gsub ?? '') === (rsub ?? '')) return 
true;
+      if (ga === ra && gact === ract && (gsub ?? '') === (rsub ?? '')) return 
true;
     }
     return false;
   }

Reply via email to