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 && (
{
@@ -189,7 +190,7 @@ export default function NodeDetailPage() {
>
Force Revert Config
-
+
}
)}
@@ -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.
+ )}
-
setShowConfig(true)}
- className="px-4 py-2 bg-indigo-700 hover:bg-indigo-600 text-white rounded-lg text-sm font-mono transition-colors"
- >
- {node.configured ? "Reassign System" : "Configure"}
-
-
sendCommand("op25_restart", "Restart OP25")}
- className="px-4 py-2 bg-raised hover:bg-gray-700 text-ink-2 rounded-lg text-sm font-mono transition-colors disabled:opacity-50"
- >
- Restart OP25
-
+ {isAdmin && (
+
setShowConfig(true)}
+ className="px-4 py-2 bg-indigo-700 hover:bg-indigo-600 text-white rounded-lg text-sm font-mono transition-colors"
+ >
+ {node.configured ? "Reassign System" : "Configure"}
+
+ )}
+ {isAdmin && (
+
sendCommand("op25_restart", "Restart OP25")}
+ className="px-4 py-2 bg-raised hover:bg-gray-700 text-ink-2 rounded-lg text-sm font-mono transition-colors disabled:opacity-50"
+ >
+ Restart OP25
+
+ )}
{isAdmin && (
{pending.map((n) => (
-
setConfigNode(n)} className="cursor-pointer">
-
-
+ // Configuring is admin-only on the server (server-26#222); for
+ // everyone else the card links to the node page instead.
+ isAdmin ? (
+
setConfigNode(n)} className="cursor-pointer">
+
+
+ ) : (
+
+ )
))}
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
- { setEditing("new"); setEditIsDuplicate(false); }}
- className="bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded-lg text-sm font-mono transition-colors"
- >
- + New System
-
+ {/* Every systems write route is require_admin_token (server-26#283);
+ operators get a read-only view. */}
+ {isAdmin && (
+ { setEditing("new"); setEditIsDuplicate(false); }}
+ className="bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded-lg text-sm font-mono transition-colors"
+ >
+ + New System
+
+ )}
{editing && (
@@ -2006,7 +2010,7 @@ export default function SystemsPage() {
{kindLabel(s)}
-
+ {isAdmin &&
openEdit(s)}
className="text-xs text-indigo-400 hover:text-indigo-300 transition-colors"
@@ -2025,15 +2029,21 @@ export default function SystemsPage() {
>
Delete
-
+
}
{/* 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 && (
+ <>
+
+
+
+
+ >
+ )}
);
})}