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;
}