added full access row to operator modal, plus a test for the last-manager guard
The "*" wildcard was never in assignablePermissions, so an operator holding it (the one the first-run wizard creates) showed every box unticked while having every right, and there was no way to take it away. Its own row fixes both; the grid is disabled while it is on, since normalisePermissions collapses "*" plus anything back to "*". That made guardManagerRemoval reachable from the UI for the first time, so it now has an integration test covering the wildcard match and the enabled filter in its SQL.
This commit is contained in:
parent
c04a8ddc6a
commit
7ad68c3983
4 changed files with 136 additions and 5 deletions
|
|
@ -4,7 +4,7 @@ import { createPortal } from "react-dom";
|
|||
import { api, errorMessage } from "../api";
|
||||
import { ActionButton } from "../components/ActionButton";
|
||||
import { Alert, EmptyRow, LoadingRow, PageFrame, QueryPanel, SectionHead } from "../components/ui";
|
||||
import { groupPermissions, permissionHint, permissionTitle } from "../permissions";
|
||||
import { groupPermissions, permissionAll, permissionHint, permissionTitle } from "../permissions";
|
||||
import type { AdminConsoleUser, AdminConsoleSystemOperator } from "../types";
|
||||
|
||||
// The operator-accounts screen. The table only reports; every change happens in
|
||||
|
|
@ -165,19 +165,42 @@ function PermissionChips({ permissions }: { permissions: string[] }) {
|
|||
//
|
||||
// The raw permission string stays as each row's tooltip, so the screen never
|
||||
// hides what is actually being stored.
|
||||
//
|
||||
// "*" gets its own row rather than a box in the grid below, because
|
||||
// assignablePermissions() deliberately leaves it out of the assignable list
|
||||
// (cmd/telesrv-admin/security.go) -- without this row an operator holding the
|
||||
// wildcard, like the one the first-run wizard creates, renders as every box
|
||||
// unticked while Has() answers true for everything, and there is no way to take
|
||||
// it away again. While it is on the grid is disabled: normalisePermissions
|
||||
// collapses "*" plus anything back to just "*", so ticking a box there would be
|
||||
// a no-op the screen would otherwise show as a change.
|
||||
function PermissionPicker({
|
||||
available,
|
||||
selected,
|
||||
onToggle,
|
||||
onToggleGroup
|
||||
onToggleGroup,
|
||||
onToggleAll
|
||||
}: {
|
||||
available: string[];
|
||||
selected: string[];
|
||||
onToggle: (permission: string, on: boolean) => void;
|
||||
onToggleGroup: (permissions: string[], on: boolean) => void;
|
||||
onToggleAll: (on: boolean) => void;
|
||||
}) {
|
||||
const full = selected.includes(permissionAll);
|
||||
return (
|
||||
<div className="permission-groups">
|
||||
<section className="permission-group">
|
||||
<div className="permission-grid">
|
||||
<label className="permission-item" title={permissionAll}>
|
||||
<input type="checkbox" checked={full} onChange={(event) => onToggleAll(event.target.checked)} />
|
||||
<span className="permission-copy">
|
||||
<strong>{"Full access"}</strong>
|
||||
<small>{"Every right below, including ones added in future updates. Turn off to pick rights individually."}</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
{groupPermissions(available).map((group) => {
|
||||
const all = group.permissions.every((p) => selected.includes(p));
|
||||
return (
|
||||
|
|
@ -190,6 +213,7 @@ function PermissionPicker({
|
|||
<button
|
||||
className="btn compact"
|
||||
type="button"
|
||||
disabled={full}
|
||||
onClick={() => onToggleGroup(group.permissions, !all)}
|
||||
>
|
||||
{all ? "Clear" : "Select all"}
|
||||
|
|
@ -200,7 +224,8 @@ function PermissionPicker({
|
|||
<label className="permission-item" key={permission} title={permission}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.includes(permission)}
|
||||
checked={full || selected.includes(permission)}
|
||||
disabled={full}
|
||||
onChange={(event) => onToggle(permission, event.target.checked)}
|
||||
/>
|
||||
<span className="permission-copy">
|
||||
|
|
@ -301,6 +326,11 @@ function OperatorModal({
|
|||
: current.filter((p) => !group.includes(p))
|
||||
)
|
||||
}
|
||||
onToggleAll={(on) =>
|
||||
setPermissions((current) =>
|
||||
on ? [permissionAll] : current.filter((p) => p !== permissionAll)
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<label className="permission-item standalone">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue