admin/accounts: show members of a group

Massimo Melina committed Dec 14, 2024 at 12:53 UTC ca6449e8f2cfa94da3d36156037ebb1f8226fff2
3 files changed +19 -4
admin/src/AccountForm.ts
+7 -2
@@ -5,7 +5,7 @@ import { BoolField, Form, MultiSelectField, NumberField } from '@hfs/mui-grid-fo
5 import { Alert, Box } from '@mui/material'
6 import { apiCall } from './api'
7 import { alertDialog, useDialogBarColors } from './dialog'
8 -import { formatTimestamp, isEqualLax, prefix, useIsMobile, wantArray } from './misc'
8 +import { formatTimestamp, isEqualLax, prefix, reactJoin, useIsMobile, wantArray } from './misc'
9 import { IconBtn, propsForModifiedValues } from './mui'
10 import { Account } from './AccountsPage'
11 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
@@ -30,6 +30,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
30 const group = !values.hasPassword
31 const ref = useRef<HTMLFormElement>()
32 const expired = Boolean(values.expire)
33 + const { members } = account
34 return h(Form, {
35 formRef: ref,
36 values,
@@ -74,6 +75,10 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
75 ...!account.admin && account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited. To disable it, act on the groups." },
76 },
77 { k: 'disable_password_change', comp: BoolField, fromField: x=>!x, toField: x=>!x, label: "Allow password change", xs: true },
78 + !members ? null
79 + : group && !members.length ? h(Box, {}, "No members")
80 + : members.length > 0 && h(Box, {}, `${members.length} members: `,
81 + reactJoin(', ', account.members?.map((u: string) => h(groups.includes(u) ? 'i' : 'span', {}, u))) ),
82 group && h(Alert, { severity: 'info' }, `To add users to this group, select the user and then click "Inherit"`),
83 { k: 'belongs', comp: MultiSelectField, label: "Inherit from groups", options: belongsOptions,
84 helperText: "Specify groups to inherit permissions from"
@@ -91,7 +96,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
96 save: {
97 ...propsForModifiedValues(isModifiedConfig(values, account)),
98 async onClick() {
94 - const { password='', password2, adminActualAccess, hasPassword, invalidated, ...withoutPassword } = values
99 + const { password='', password2, adminActualAccess, hasPassword, invalidated, canLogin, members, ...withoutPassword } = values
100 if (add) {
101 const got = await apiCall('add_account', withoutPassword)
102 if (password)
admin/src/AccountsPage.ts
+2 -1
@@ -147,7 +147,8 @@ export default function AccountsPage() {
147 hasPassword: sel === 'new-user',
148 adminActualAccess: false,
149 invalidated: undefined,
150 - canLogin: true
150 + canLogin: true,
151 + members: [],
152 } satisfies Account
153 }
154
src/api.accounts.ts
+10 -1
@@ -8,7 +8,7 @@ import {
8 import _ from 'lodash'
9 import { HTTP_BAD_REQUEST, HTTP_CONFLICT, HTTP_NOT_FOUND } from './const'
10 import { getCurrentUsername, invalidateSessionBefore } from './auth'
11 -import { apiAssertTypes, onlyTruthy } from './misc'
11 +import { apiAssertTypes, onlyTruthy, with_ } from './misc'
12
13 function prepareAccount(ac: Account | undefined) {
14 return ac && {
@@ -18,6 +18,15 @@ function prepareAccount(ac: Account | undefined) {
18 adminActualAccess: accountCanLoginAdmin(ac),
19 canLogin: accountHasPassword(ac) ? accountCanLogin(ac) : undefined,
20 invalidated: invalidateSessionBefore.get(ac.username),
21 + members: with_(Object.values(accountsConfig.get()), accounts => {
22 + const ret = []
23 + let news = [ac.username]
24 + while (news.length) {
25 + news = accounts.filter(a => a.belongs?.some(x => news.includes(x))).map(x => x.username)
26 + ret.push(...news)
27 + }
28 + return ret.sort()
29 + })
30 }
31 }
32