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 88cc989 ui: grant viewer/maintainer overview:read + menu-visibility
matrix on Roles page
88cc989 is described below
commit 88cc989b1fb885424965b3c9e3653bd483a01750
Author: Wu Sheng <[email protected]>
AuthorDate: Wed May 20 17:07:59 2026 +0800
ui: grant viewer/maintainer overview:read + menu-visibility matrix on Roles
page
- horizon.example.yaml: viewer and maintainer roles now include
overview:read — the overview dashboards are read-only observability and
belong in the base viewer experience (matches operator/admin which
already had it). The Overviews sidebar section + /api/overview/* both
gate on this verb, so they now work for viewers.
- RolesView: add a "Menu visibility" matrix — sidebar item × role, ✔/—,
computed live from the policy with the same verb gates AppSidebar uses
(last column shows the gating read verb). Makes "what does this verb
unlock in the nav" concrete per role; `null` verb = any signed-in user
(the cap-gated data layers).
---
apps/ui/src/features/admin/roles/RolesView.vue | 89 ++++++++++++++++++++++++++
horizon.example.yaml | 8 ++-
2 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/apps/ui/src/features/admin/roles/RolesView.vue
b/apps/ui/src/features/admin/roles/RolesView.vue
index 2a34cd2..2c79822 100644
--- a/apps/ui/src/features/admin/roles/RolesView.vue
+++ b/apps/ui/src/features/admin/roles/RolesView.vue
@@ -58,6 +58,36 @@ function hasVerb(grants: readonly string[], required:
string): boolean {
const roleNames = computed(() => Object.keys(status.value?.rbac.roles ??
{}).sort(rolePriority));
+/** Grants for a role from the live policy. */
+function grantsFor(role: string): readonly string[] {
+ return status.value?.rbac.roles?.[role] ?? [];
+}
+/** Sidebar menu → the read verb that gates its visibility (mirrors
+ * AppSidebar.vue). Drives the visibility matrix so operators can read,
+ * per role, exactly which navigation items appear. `null` verb = shown
+ * to any signed-in user (the core data layers, cap-gated only). */
+const MENU_GATES: ReadonlyArray<{ label: string; verb: string | null }> = [
+ { label: 'Layers (per-layer data)', verb: null },
+ { label: 'Alarms', verb: 'alarms:read' },
+ { label: 'Overviews', verb: 'overview:read' },
+ { label: 'Cluster status', verb: 'cluster:read' },
+ { label: 'Metrics Inspect', verb: 'inspect:read' },
+ { label: 'Alerting rules', verb: 'alarm-rule:read' },
+ { label: 'Live debugger · Capture history', verb: 'live-debug:read' },
+ { label: 'DSL Management', verb: 'rule:read' },
+ { label: 'Overview templates', verb: 'overview:read' },
+ { label: 'Layer dashboards', verb: 'dashboard:read' },
+ { label: 'Alert page', verb: 'alarm-setup:read' },
+ { label: 'Global defaults', verb: 'setup:read' },
+ { label: 'Users', verb: 'user:read' },
+ { label: 'Auth status', verb: 'auth:read' },
+ { label: 'Roles & permissions', verb: 'role:read' },
+];
+/** Is the menu row visible to the role? `null` verb ⇒ any signed-in user. */
+function menuVisible(role: string, verb: string | null): boolean {
+ return verb === null ? true : hasVerb(grantsFor(role), verb);
+}
+
function rolePriority(a: string, b: string): number {
const order = ['viewer', 'maintainer', 'operator', 'admin'];
const ia = order.indexOf(a);
@@ -301,6 +331,39 @@ function grantsOf(role: string): string[] {
</div>
</section>
+ <!-- Menu visibility matrix: which sidebar items each role sees.
+ Computed live from the policy via the same verb gates the
+ sidebar uses, so it stays honest if roles are reconfigured. -->
+ <section class="sw-card menu-matrix">
+ <header class="card-head">
+ <h3>Menu visibility</h3>
+ <span class="muted">which sidebar items each role sees · gated by
the read verb in the last column (UI hides; the BFF enforces the same
server-side)</span>
+ </header>
+ <div class="matrix-scroll">
+ <table class="matrix">
+ <thead>
+ <tr>
+ <th class="m-menu">Menu</th>
+ <th v-for="r in roleNames" :key="r" class="m-role">
+ <span class="pill" :class="rolePill(r)">{{ r }}</span>
+ </th>
+ <th class="m-verb">Read verb</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="row in MENU_GATES" :key="row.label">
+ <td class="m-menu">{{ row.label }}</td>
+ <td v-for="r in roleNames" :key="r" class="m-cell">
+ <span v-if="menuVisible(r, row.verb)" class="yes"
title="visible">✔</span>
+ <span v-else class="no" title="hidden">—</span>
+ </td>
+ <td class="m-verb"><code>{{ row.verb ?? 'any signed-in'
}}</code></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </section>
+
<!-- Per-group cards: Menu scope (left) + Actions (right) -->
<section v-for="g in groupedVerbs" :key="g.title" class="sw-card
group-card">
<header class="card-head">
@@ -610,4 +673,30 @@ function grantsOf(role: string): string[] {
}
.rule-k { font-size: 11.5px; font-weight: 600; color: var(--sw-fg-0); }
.rule-v { font-size: 11.5px; color: var(--sw-fg-2); margin-top: 4px;
line-height: 1.55; }
+
+.menu-matrix { margin-bottom: 14px; }
+.matrix-scroll { overflow-x: auto; }
+.matrix {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 11.5px;
+}
+.matrix th,
+.matrix td {
+ padding: 6px 10px;
+ border-bottom: 1px solid var(--sw-line);
+ text-align: center;
+ white-space: nowrap;
+}
+.matrix thead th { border-bottom: 2px solid var(--sw-line); }
+.matrix .m-menu { text-align: left; color: var(--sw-fg-0); }
+.matrix .m-verb { text-align: left; }
+.matrix .m-verb code {
+ font-family: var(--sw-mono);
+ font-size: 10.5px;
+ color: var(--sw-fg-3);
+}
+.matrix tbody tr:hover { background: var(--sw-bg-2); }
+.matrix .yes { color: var(--sw-ok, #34d399); font-weight: 700; }
+.matrix .no { color: var(--sw-fg-3); }
</style>
diff --git a/horizon.example.yaml b/horizon.example.yaml
index 3e9b50a..224e90e 100644
--- a/horizon.example.yaml
+++ b/horizon.example.yaml
@@ -122,9 +122,9 @@ auth:
rbac:
enabled: true
roles:
- # Data catalog only. Deliberately NOT `*:read` so a viewer can't
- # accidentally see rule definitions, live-debug sessions, setup
- # screens, or platform internals.
+ # Data catalog + read-only overview dashboards. Deliberately NOT
+ # `*:read` so a viewer can't accidentally see rule definitions,
+ # live-debug sessions, setup screens, or platform internals.
viewer:
- metrics:read
- alarms:read
@@ -132,6 +132,7 @@ rbac:
- logs:read
- topology:read
- profile:read
+ - overview:read
# Viewer + platform monitoring (OAP cluster + module inspector).
maintainer:
@@ -141,6 +142,7 @@ rbac:
- logs:read
- topology:read
- profile:read
+ - overview:read
- cluster:read
- inspect:read