From 9702b90d407dfd98e104e6ee78e8549fc1edcef3 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 25 Oct 2026 22:07:22 -0400 Subject: [PATCH 1/2] fix(frontend): hide admin-only node and system controls from non-admins (#222, #283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI gate only — no route auth changes. Node page: override Ack/Force Revert, Configure/Reassign and Restart OP25 are admin-only; non-admins on an unconfigured node get a "an admin needs to assign it" note. /nodes "Needs Configuration" cards open the config modal only for admins, and link to the node page otherwise. /systems: New/Edit/Duplicate/Delete and the AI-flags, area-context, talkgroup-pending and vocabulary panels are admin-only; operators get a read-only list. (Delete's missing catch was fixed in #290.) Widening any of these routes to org owners/operators is a separate, security-reviewed backend change. Frontend unverified: no Node on the authoring box (#207). Closes #222 Closes #283 Co-Authored-By: Claude Opus 5.5 --- drb-frontend/app/nodes/[id]/page.tsx | 40 +++++++++++++++++----------- drb-frontend/app/nodes/page.tsx | 12 ++++++--- drb-frontend/app/systems/page.tsx | 34 ++++++++++++++--------- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/drb-frontend/app/nodes/[id]/page.tsx b/drb-frontend/app/nodes/[id]/page.tsx index 0174722..72aa159 100644 --- a/drb-frontend/app/nodes/[id]/page.tsx +++ b/drb-frontend/app/nodes/[id]/page.tsx @@ -160,7 +160,8 @@ export default function NodeDetailPage() { <> No timeout is currently enforced (permanent override). )}

-
+ {/* Override routes are require_service_key_or_admin (server-26#222). */} + {isAdmin &&
{node.override_timeout_at && ( -
+
} )} @@ -245,20 +246,29 @@ export default function NodeDetailPage() { )} {/* Actions */} + {/* Configure and Restart are admin-only on the server (server-26#222); + non-admins get a note instead of a button that 403s. */} + {!isAdmin && !node.configured && ( +

This node isn't configured yet. An admin needs to assign it a radio system.

+ )}
- - + {isAdmin && ( + + )} + {isAdmin && ( + + )} {isAdmin && (
diff --git a/drb-frontend/app/systems/page.tsx b/drb-frontend/app/systems/page.tsx index c7a9981..5f81ad5 100644 --- a/drb-frontend/app/systems/page.tsx +++ b/drb-frontend/app/systems/page.tsx @@ -1953,12 +1953,16 @@ export default function SystemsPage() {

Systems

- + {/* Every systems write route is require_admin_token (server-26#283); + operators get a read-only view. */} + {isAdmin && ( + + )}
{editing && ( @@ -2006,7 +2010,7 @@ export default function SystemsPage() { {kindLabel(s)}
-
+ {isAdmin &&
-
+
} {/* GET /tokens and PUT /tokens/{id}/prefer are platform-admin only (server-26#204 item 6), so for anyone else this panel could neither list the pool nor save a choice (server-26#213 item 4). */} {isAdmin && } - - - - + {/* All admin-only routes (server-26#283), incl. area-context and + talkgroup-pending GETs and every vocabulary write. */} + {isAdmin && ( + <> + + + + + + )} ); })} From e8084357c3d9c1938eb92e553320c75e764a1b75 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 25 Oct 2026 22:09:57 -0400 Subject: [PATCH 2/2] fix(map): keep muted popup text AA on Leaflet's white popups Token text-ink-muted is #8C98AE in dark mode, ~2.9:1 on the always-white popups (node, incident, path-stop, AIS, 511 camera). Scoped override to #5B6577 (~6:1). Found in tonight's regression review. Co-Authored-By: Claude Opus 5.5 --- drb-frontend/app/globals.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drb-frontend/app/globals.css b/drb-frontend/app/globals.css index 9dcc7c2..372887b 100644 --- a/drb-frontend/app/globals.css +++ b/drb-frontend/app/globals.css @@ -199,6 +199,13 @@ html:not(.dark) .border-indigo-800 { border-color: #a5b4fc !important; } filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9) saturate(0.6); } +/* Leaflet popups stay white in both themes (their body text is a fixed + * gray-900), so the theme's muted ink — light grey in dark mode, ~2.9:1 on + * white — is pinned to a grey that holds AA on white (~6:1). */ +.leaflet-popup-content .text-ink-muted { + color: #5B6577; +} + /* Pinned 511 camera tiles (MapView DotCameraLayer): strip Leaflet's tooltip * padding and let the tile's own theme colours show, so the tile is no bigger * than the video it holds. */