mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 22:25:30 +00:00
fix(mesh): hide node and stack management controls from non-admins (#1284)
* fix(mesh): hide node and stack management controls from non-admins The Routing tab rendered the per-node mesh enable/disable toggle and the stack opt-in/opt-out controls for any Admiral-tier user, but those backend routes require the admin role. A non-admin viewer on an Admiral instance saw controls that returned 403. Thread a canManage flag (true only for admins) from the Fleet view into the Routing tab, its node cards, and the opt-in sheet so non-admins get a read-only Routing tab: the enable/disable toggle, add-stack, and opt-in/opt-out controls are hidden, while status, aliases, topology, activity, diagnostics, and the alias test probe stay available. This mirrors the Federation tab's existing read-only treatment for non-admins. Add backend route-gating tests covering the tier and admin-role guards on every mesh route, and frontend render-gate tests for the node card and the opt-in sheet in both density layouts. * refactor(mesh): require canManage on the routing-node-card primitive Remove the permissive `canManage = true` default on the shared routing-node-card primitive so a new call site cannot render the management controls without an explicit decision. Every current caller already passes the flag; the type now enforces it. Drop the omitted-prop test, which covered a state the compiler now prevents.
This commit is contained in:
@@ -14,9 +14,11 @@ interface Props {
|
||||
nodeName: string;
|
||||
onChanged: () => void;
|
||||
onViewTopology?: (stack: string) => void;
|
||||
/** Opt-in/out is admin-only on the backend; non-admins see the list read-only. */
|
||||
canManage: boolean;
|
||||
}
|
||||
|
||||
export function MeshOptInSheet({ open, onOpenChange, nodeId, nodeName, onChanged, onViewTopology }: Props) {
|
||||
export function MeshOptInSheet({ open, onOpenChange, nodeId, nodeName, onChanged, onViewTopology, canManage }: Props) {
|
||||
const [stacks, setStacks] = useState<MeshStackEntry[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -93,6 +95,7 @@ export function MeshOptInSheet({ open, onOpenChange, nodeId, nodeName, onChanged
|
||||
<p className="text-sm text-stat-subtitle leading-snug">
|
||||
Adding a stack lets its services be reached from other meshed stacks by hostname.
|
||||
Toggling a stack triggers a redeploy on its node so the routing override applies.
|
||||
{!canManage && ' Changing mesh membership requires an administrator.'}
|
||||
</p>
|
||||
|
||||
{loading && (
|
||||
@@ -132,13 +135,15 @@ export function MeshOptInSheet({ open, onOpenChange, nodeId, nodeName, onChanged
|
||||
<Workflow className="w-3 h-3 mr-1" /> Topology
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant={stack.optedIn ? 'outline' : 'default'}
|
||||
onClick={() => setConfirmStack(stack)}
|
||||
>
|
||||
{stack.optedIn ? 'Remove from mesh' : 'Add to mesh'}
|
||||
</Button>
|
||||
{canManage && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant={stack.optedIn ? 'outline' : 'default'}
|
||||
onClick={() => setConfirmStack(stack)}
|
||||
>
|
||||
{stack.optedIn ? 'Remove from mesh' : 'Add to mesh'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user