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 c262b2c docs+changelog: table widget, edit-locally→publish flow,
route verb gate
c262b2c is described below
commit c262b2c9cf743248df9ccc95389e11065eda9e6c
Author: Wu Sheng <[email protected]>
AuthorDate: Thu May 21 21:44:51 2026 +0800
docs+changelog: table widget, edit-locally→publish flow, route verb gate
CHANGELOG (0.5.0): Dashboards & templates section (time picker drives
dashboards, the table widget + K8S/upstream alignment, edit-locally →
Sync all publish, local/remote conflict prompt), a Traces section (v1/v2
auto-select + banner, span-kind coloring), plus RBAC route-gating, LDAP
service-bind, TTL-on-non-BanyanDB, and the flame-graph single-tooltip fix.
Docs: dashboard-widgets gains the `table` type (fields, example, MQE
mapping); layer-templates "Admin editor" rewritten for the edit-locally
→ publish + local/remote-conflict workflow (the old removed save route is
gone); rbac notes whole-page verb gating.
---
CHANGELOG.md | 52 +++++++++++++++++++++++++++++++++--
docs/access-control/rbac.md | 2 +-
docs/components/dashboard-widgets.md | 41 ++++++++++++++++++++++++---
docs/customization/layer-templates.md | 18 ++++++++++--
4 files changed, 102 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67953ef..24aedda 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,8 +26,9 @@ during packaging and validated against a deny-list before
signing.
its full attributes, selection lives on the checkbox, anchored pop-out — a
refresh button on every task list, Intl-formatted times, and a hover-info
frame on the flame graph. Flame-graph thrash on re-analyze is gone.
-- The shared flame graph fixes "% of root" (it read a never-aggregated count)
- and highlights the selected frame across all four profilers.
+- The shared flame graph fixes "% of root" (it read a never-aggregated count),
+ highlights the selected frame across all four profilers, and shows a single
+ hover card (the library's duplicate native tooltip is suppressed).
- After creating any profiling task (trace / async / eBPF / network / pprof)
the list now polls up to 4× at 10s until the new task shows up, instead of
leaving a stale pre-create list.
@@ -44,11 +45,48 @@ during packaging and validated against a deny-list before
signing.
- Two new read-only operate pages: **Data retention** (TTL — `getRecordsTTL` /
`getMetricsTTL`) and **OAP configuration** (the admin-port config dump, with
OAP-masked secrets). Gated on new `ttl:read` / `config:read` verbs granted to
- maintainer and above.
+ maintainer and above. Data retention now loads on non-BanyanDB backends too
+ (the `metadata` TTL field is optional).
- The operate sidebar now leads with a single **Platform monitoring** group
(cluster status, data retention, OAP configuration) above the per-layer
self-observability dashboards.
+### Dashboards & templates
+
+- **The global time picker now drives dashboards.** Layer dashboards query
+ OAP at the picker's window and precision (MINUTE / HOUR / DAY) instead of a
+ fixed last-hour minute window, and line charts label the x-axis with real
+ times per step (e.g. `MM-DD` for a 30-day view) rather than `-Nm`.
+- **New `table` widget** for label-dimensioned metrics — pod phase per service,
+ node condition, deployment replicas, etc. — rendered as one column per label
+ (e.g. `Condition | Node`) instead of a scalar card or a misleading flat line.
+ The K8S dashboards (and kong / mongodb / elasticsearch) now use it where
+ upstream booster-ui does; widgets that were charting a single `latest(…)`
+ value as a line are now cards. The K8S Cluster view is realigned to the
+ upstream layout (totals cards · resource lines · status tables).
+- **Edit locally, publish on your terms.** Saving a dashboard/overview template
+ now writes the local bundled copy (so the edit renders immediately for
+ preview) and marks it diverged — nothing reaches OAP until you press
+ **Sync all to OAP**, which pushes only the templates that differ, behind a
+ confirmation listing exactly what will be written. A post-save tip spells out
+ that the change is local-only until published.
+- **Local-vs-remote, made explicit.** When local edits diverge from OAP, a
+ per-session prompt (by menu name, not file name) asks which to render —
+ **keep my local edits** (preview) or **use live** (which overwrites the local
+ copy with the remote version, confirmed). The layer-templates admin page
+ carries the same Local/Remote display toggle next to Sync all, and a
+ **Diverged only** filter; each diverged layer shows a yellow warning icon in
+ the sidebar.
+
+### Traces
+
+- The native trace view auto-selects OAP's trace-query API — `queryTraces`
+ (whole trace inline, BanyanDB) vs `queryBasicTraces` (segment list + a
+ per-trace fetch on click, every other backend) — and a banner states which
+ is in use; in segment-list mode the list reads "Segments" and a click loads
+ the full trace.
+- Span kind (Entry / Exit / Local) renders as a colored word, not a filled
pill.
+
### Auth, RBAC & resilience
- Every OAP call — GraphQL, admin REST, and Zipkin — now carries the
@@ -56,6 +94,14 @@ during packaging and validated against a deny-list before
signing.
- The sidebar is RBAC-gated by read verb, the Roles page shows a per-role
menu-visibility matrix, and the Users page labels per-node "Active (24h)" /
"Last seen" honestly (these are tracked per BFF replica, not cluster-wide).
+- **Routes are verb-gated, not just menus.** A user without the required read
+ verb is bounced from a restricted page (e.g. a viewer can no longer reach
+ Cluster Status via the topbar OAP chip or a direct URL); the chip only links
+ there for `cluster:read`. This sits on top of the existing per-route BFF verb
+ enforcement.
+- **LDAP** resolves group membership with the service account, not the
+ logging-in user — directories that hide the group subtree from ordinary users
+ no longer collapse every login to the fallback role.
- When OAP is unreachable the menu and admin loaders fall back to bundled
templates, and non-JSON OAP responses surface a clear diagnostic.
diff --git a/docs/access-control/rbac.md b/docs/access-control/rbac.md
index 8a410b0..c246390 100644
--- a/docs/access-control/rbac.md
+++ b/docs/access-control/rbac.md
@@ -1,6 +1,6 @@
# RBAC: Roles & Verbs
-Horizon enforces access at the BFF on every HTTP request. The UI hides
controls based on the verbs the session reports, but the enforcement is
server-side — a forged UI cannot escalate. This page is the full reference for
the verb vocabulary, the four built-in roles, and how grants are matched
against requests.
+Horizon enforces access at the BFF on every HTTP request. The UI hides
controls based on the verbs the session reports, but the enforcement is
server-side — a forged UI cannot escalate. The UI also gates whole pages by
verb: navigating to a restricted page you lack the verb for (by URL or a stray
link) redirects you home, so a viewer can't land on a maintainer page even if
its data comes from a shared endpoint. This page is the full reference for the
verb vocabulary, the four built-in ro [...]
## Model
diff --git a/docs/components/dashboard-widgets.md
b/docs/components/dashboard-widgets.md
index 18930b5..ad4b8cb 100644
--- a/docs/components/dashboard-widgets.md
+++ b/docs/components/dashboard-widgets.md
@@ -1,6 +1,6 @@
# Dashboard Widgets
-Four widget types render on per-layer dashboards. Each `widget.type` you set
in a template selects one of them.
+Five widget types render on per-layer dashboards. Each `widget.type` you set
in a template selects one of them.
## Grid context
@@ -19,13 +19,15 @@ interface DashboardWidget {
id: string;
title: string;
tip?: string;
- type: 'card' | 'line' | 'top' | 'record';
+ type: 'card' | 'line' | 'top' | 'record' | 'table';
expressions: string[];
expressionLabels?: string[];
expressionUnits?: string[];
expressionAxes?: number[];
unit?: string;
format?: 'int' | 'decimal' | 'compact';
+ tableHeaders?: [string, string];
+ showTableValues?: boolean;
span?: number;
rowSpan?: number;
visibleWhen?: string;
@@ -35,12 +37,14 @@ interface DashboardWidget {
| Field | Notes |
|---|---|
-| `expressions[]` | MQE expressions. `card` typically uses one; `line`
one-per-series; `top` one-per-tab. |
+| `expressions[]` | MQE expressions. `card` typically uses one; `line`
one-per-series; `top` one-per-tab; `table` one labeled `latest(…)` metric. |
| `expressionLabels[]` | Used by `top` for tab labels and by `line` for legend
names. |
| `expressionUnits[]` | Per-expression unit override (mixed-unit charts). |
| `expressionAxes[]` | `0` = left axis (default), `1` = right axis. |
| `unit` | Widget-level default. |
| `format` | `int`, `decimal`, `compact`. |
+| `tableHeaders` | `table` only — `[, valueHeader]`; the value column's
header. Label columns are headed by their dimension name. |
+| `showTableValues` | `table` only — show the value column. `false` for
presence-only lists (e.g. node conditions). Default `true`. |
| `visibleWhen` | Predicate. `#entity.<key>` (hides the widget unless the
named entity is selected) or `<metric> has value` (hides unless the metric
returns data). |
| `layerScope` | Evaluate against the whole layer rather than the selected
service. |
@@ -190,6 +194,34 @@ The data source returns a record set (rows × typed
columns) rather than a numer
- Renders as a dense table with column headers from the record's typed fields.
- Supports sort, filter, and pagination.
+## `table`
+
+**Renders:** A key→value table for a **labeled** `latest(…)` metric — one row
per label combination, one column per label dimension, plus an optional value
column.
+
+### When to use
+
+For multi-dimensional status metrics that a scalar `card` can't summarize and
a time-series `line` misrepresents — e.g. pod phase per service, node
condition, deployment replicas, queue depth per topic. The MQE is a single
`latest(<labeled metric>)`; each returned label set becomes a row.
+
+### Example
+
+```json
+{
+ "id": "node_status",
+ "title": "Node Status",
+ "type": "table",
+ "expressions": ["latest(k8s_cluster_node_status)"],
+ "showTableValues": false
+}
+```
+
+Renders columns `Condition | Node` (one per label key). A widget like
Deployment Replicas sets `"showTableValues": true` and `"tableHeaders": ["",
"Replicas"]` to show the value column headed "Replicas".
+
+### Behavior
+
+- Columns are derived from the union of label keys across rows; headers are
the dimension names (the value column header comes from `tableHeaders[1]`).
+- `showTableValues: false` drops the value column for presence-only lists
(where the value is always 1).
+- Scrolls within the widget when rows overflow.
+
## Visibility predicates
`visibleWhen` lets a widget hide itself based on context:
@@ -220,7 +252,8 @@ The predicate is evaluated on every data refresh; the
widget disappears (rather
| MQE outermost call | Widget type |
|---|---|
-| `latest(...)`, `max(...)`, `min(...)`, `avg(<plain>)`, `sum(<plain>)` |
`card` |
+| `latest(...)`, `max(...)`, `min(...)`, `avg(<plain>)`, `sum(<plain>)`
(single scalar) | `card` |
+| `latest(<labeled metric>)` returning many label sets (status / phase /
condition per entity) | `table` |
| `rate(...)`, `increase(...)`, `relabels(...)`, `aggregate_labels(...)`
without scalar collapse, `histogram*(...)` | `line` |
| `top_n(...)` returning labeled list | `top` |
| Record-shaped output (slow SQL, slow gRPC) | `record` |
diff --git a/docs/customization/layer-templates.md
b/docs/customization/layer-templates.md
index c5bcdca..7d61248 100644
--- a/docs/customization/layer-templates.md
+++ b/docs/customization/layer-templates.md
@@ -325,11 +325,23 @@ Service-name parsing rule. Extracts a cluster (or other
token) from the OAP-repo
When set, the layer's service list groups by `cluster`. Without it, services
are listed flat.
-## Admin editor
+## Admin editor — edit locally, publish on your terms
-Layer templates are editable at runtime via `/admin/layer-templates` (verb
`dashboard:write`). The editor shows the JSON tree with per-field type-aware
controls. Changes are validated against the same schema as the bundled files,
then written through `POST /api/admin/layer-templates/:key`.
+Layer templates are editable at runtime via `/admin/layer-templates` (verb
`dashboard:write`). The editor shows the JSON tree with per-field type-aware
controls; changes are validated against the same schema as the bundled files.
-Bundled templates remain in-place; admin edits override them per-instance and
persist in the configured location.
+The save/publish model has two steps:
+
+1. **Save locally.** "Save locally" writes your edit to the local bundled copy
and renders it immediately for preview — it does **not** touch OAP. The
template now shows as **diverged** (local differs from what OAP serves), the
row carries a *Synced from OAP — N diverged* banner, and the affected layers
show a yellow warning icon in the sidebar. Save works even when OAP is
unreachable.
+2. **Publish.** **Sync all to OAP** pushes the diverged templates to OAP (the
runtime source of truth) — only the ones that differ — behind a confirmation
that lists exactly what will be written. After publishing, the template is
synced and everyone sees it.
+
+A **Diverged only** filter and a **Showing: Local / Remote** display toggle
sit at the top of the page; **Show diff** opens a side-by-side bundled-vs-OAP
comparison.
+
+### Local vs. remote conflicts
+
+OAP is the source of truth at runtime, so by default the app renders the
OAP-stored version. When your local edits diverge, a per-session prompt
(listing the affected items by menu name) asks which to render:
+
+- **Keep my local edits** — render your local copy for preview; publish later
with Sync all.
+- **Use live** — overwrite your local copy with the remote (OAP) version. This
**discards your local edits** and is confirmed first; use it when OAP holds the
newer version.
## Bundled examples