Allow editing non-Administrators group access from the Groups tab.

Load a group into the form to change scope and permissions; keep Administrators locked like delete.
This commit is contained in:
2026-07-18 15:29:22 +02:00
parent 5d6f65f3cd
commit b93b7519ec
3 changed files with 306 additions and 37 deletions
+99 -32
View File
@@ -208,12 +208,15 @@ function GroupsConfigTab() {
const [isLoadingGroups, setIsLoadingGroups] = useState(true)
const [groupsLoadError, setGroupsLoadError] = useState<string | null>(null)
const [editingGroupName, setEditingGroupName] = useState<string | null>(null)
const [draftGroupName, setDraftGroupName] = useState('')
const [draftScopeKind, setDraftScopeKind] = useState<GroupScopeKind>('node')
const [draftScopeName, setDraftScopeName] = useState('')
const [draftPermissions, setDraftPermissions] = useState<string[]>([])
const [submitError, setSubmitError] = useState<string | null>(null)
const isEditing = editingGroupName !== null
useEffect(() => {
let isCancelled = false
@@ -245,6 +248,27 @@ function GroupsConfigTab() {
}
}, [])
function clearGroupForm() {
setEditingGroupName(null)
setDraftGroupName('')
setDraftScopeKind('node')
setDraftScopeName('')
setDraftPermissions([])
setSubmitError(null)
}
function handleEditGroup(group: Group) {
if (group.name === ADMINISTRATORS_GROUP_NAME) {
return
}
setEditingGroupName(group.name)
setDraftGroupName(group.name)
setDraftScopeKind(group.scope_kind)
setDraftScopeName(group.scope_name)
setDraftPermissions([...group.permissions])
setSubmitError(null)
}
function togglePermission(permission: string, checked: boolean) {
setDraftPermissions((prev) => {
if (checked) {
@@ -261,6 +285,11 @@ function GroupsConfigTab() {
async function handleSaveGroup() {
setSubmitError(null)
if (draftGroupName.trim() === ADMINISTRATORS_GROUP_NAME) {
setSubmitError('Administrators group cannot be created or modified here')
return
}
try {
const response = await upsertGroup({
name: draftGroupName,
@@ -270,10 +299,7 @@ function GroupsConfigTab() {
})
setGroups(response.groups)
setDraftGroupName('')
setDraftScopeKind('node')
setDraftScopeName('')
setDraftPermissions([])
clearGroupForm()
} catch (err) {
setSubmitError(
err instanceof Error ? err.message : 'Failed to save group',
@@ -287,6 +313,9 @@ function GroupsConfigTab() {
try {
const response = await deleteGroup(groupName)
setGroups(response.groups)
if (editingGroupName === groupName) {
clearGroupForm()
}
} catch (err) {
setSubmitError(
err instanceof Error ? err.message : 'Failed to delete group',
@@ -308,38 +337,60 @@ function GroupsConfigTab() {
<p className="config-empty">No groups configured yet.</p>
) : (
<ul className="groups-ul">
{groups.map((group) => (
<li key={group.name} className="groups-li">
<div className="groups-li-content">
<div className="groups-li-title">{group.name}</div>
<div className="groups-li-meta">
Scope: {group.scope_kind}: {group.scope_name}
</div>
<div className="groups-li-meta">
Permissions: {group.permissions.join(', ')}
</div>
</div>
<button
type="button"
className="groups-delete"
disabled={group.name === ADMINISTRATORS_GROUP_NAME}
title={
group.name === ADMINISTRATORS_GROUP_NAME
? 'Administrators group cannot be deleted'
: undefined
{groups.map((group) => {
const isAdministrators = group.name === ADMINISTRATORS_GROUP_NAME
const isSelected = editingGroupName === group.name
return (
<li
key={group.name}
className={
isSelected ? 'groups-li groups-li-selected' : 'groups-li'
}
onClick={() => void handleDeleteGroup(group.name)}
>
Delete
</button>
</li>
))}
<div className="groups-li-content">
<div className="groups-li-title">{group.name}</div>
<div className="groups-li-meta">
Scope: {group.scope_kind}: {group.scope_name}
</div>
<div className="groups-li-meta">
Permissions: {group.permissions.join(', ')}
</div>
</div>
<div className="groups-li-actions">
{!isAdministrators ? (
<button
type="button"
className="groups-edit"
onClick={() => handleEditGroup(group)}
>
Edit
</button>
) : null}
<button
type="button"
className="groups-delete"
disabled={isAdministrators}
title={
isAdministrators
? 'Administrators group cannot be deleted'
: undefined
}
onClick={() => void handleDeleteGroup(group.name)}
>
Delete
</button>
</div>
</li>
)
})}
</ul>
)}
</div>
<div className="groups-form">
<p className="config-hint">Create or update a group</p>
<p className="config-hint">
{isEditing ? 'Edit group' : 'Create group'}
</p>
{submitError ? (
<p className="config-empty groups-error" role="alert">
@@ -353,6 +404,7 @@ function GroupsConfigTab() {
id="group-name"
className="config-input"
value={draftGroupName}
readOnly={isEditing}
onChange={(event) => {
setDraftGroupName(event.target.value)
}}
@@ -408,9 +460,24 @@ function GroupsConfigTab() {
</div>
</div>
<button type="button" className="groups-save" onClick={() => void handleSaveGroup()}>
Save group
</button>
<div className="groups-form-actions">
{isEditing ? (
<button
type="button"
className="groups-new"
onClick={() => clearGroupForm()}
>
New group
</button>
) : null}
<button
type="button"
className="groups-save"
onClick={() => void handleSaveGroup()}
>
{isEditing ? 'Save changes' : 'Save group'}
</button>
</div>
</div>
</div>
)