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
 

Reply via email to