better code: split files

Massimo Melina committed Jun 5, 2022 at 13:51 UTC 61701113c52a45b19cda6343b3ff373ac1e75b0d
3 files changed +96 -90
admin/src/AccountForm.ts new
+92
@@ -0,0 +1,92 @@
1 +import { createElement as h, useEffect, useRef, useState } from 'react'
2 +import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
3 +import { Box, Button } from '@mui/material'
4 +import { apiCall } from './api'
5 +import { alertDialog } from './dialog'
6 +import { isEqualLax, modifiedSx } from './misc'
7 +import { Account, account2icon } from './AccountsPage'
8 +import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
9 +
10 +interface FormProps { account: Account, groups: string[], done: (username: string)=>void, close: ()=>void }
11 +export default function AccountForm({ account, done, groups, close }: FormProps) {
12 + const [values, setValues] = useState<Account & { password?: string, password2?: string }>(account)
13 + const [belongsOptions, setBelongOptions] = useState<string[]>([])
14 + useEffect(() => {
15 + setValues(account)
16 + setBelongOptions(groups.filter(x => x !== account.username ))
17 + ref.current?.querySelector('input')?.focus()
18 + }, [JSON.stringify(account)]) //eslint-disable-line
19 + const add = !account.username
20 + const group = !values.hasPassword
21 + const ref = useRef<HTMLFormElement>()
22 + return h(Form, {
23 + formRef: ref,
24 + values,
25 + set(v, k) {
26 + setValues({ ...values, [k]: v })
27 + },
28 + addToBar: [
29 + h(Button, { onClick: close, sx: { ml: 2 } }, "Close"),
30 + h(Box, { flex:1 }),
31 + account2icon(values, { fontSize: 'large', sx: { p: 1 }})
32 + ],
33 + fields: [
34 + { k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, xl: group ? 12 : 4,
35 + getError: v => v !== account.username && apiCall('get_account', { username: v }).then(() => "already used", () => false),
36 + },
37 + !group && { k: 'password', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', required: add,
38 + label: add ? "Password" : "Change password"
39 + },
40 + !group && { k: 'password2', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
41 + getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
42 + { k: 'ignore_limits', comp: BoolField, xl: 6,
43 + helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
44 + { k: 'admin', comp: BoolField, xl: 6, fromField: (v:boolean) => v||null, label: "Permission to access Admin interface",
45 + helperText: "It's THIS interface you are using right now.",
46 + ...account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited" },
47 + },
48 + { k: 'belongs', comp: MultiSelectField, label: "Inherits from", options: belongsOptions,
49 + helperText: "Specify groups to inherit permissions from."
50 + + (belongsOptions.length ? '' : " There are no groups available, create one first.")
51 + },
52 + { k: 'redirect', helperText: "If you want this account to be redirected to a specific folder/address at login time" },
53 + ],
54 + onError: alertDialog,
55 + save: {
56 + sx: modifiedSx( !isEqualLax(values, account)),
57 + async onClick() {
58 + const { password='', password2, adminActualAccess, ...withoutPassword } = values
59 + const { username } = values
60 + if (add) {
61 + await apiCall('add_account', withoutPassword)
62 + if (password)
63 + try { await apiNewPassword(username, password) }
64 + catch(e) {
65 + apiCall('del_account', { username }).then() // best effort, don't wait
66 + throw e
67 + }
68 + done(username)
69 + return alertDialog("Account created", 'success')
70 + }
71 + await apiCall('set_account', {
72 + username: account.username,
73 + changes: withoutPassword,
74 + })
75 + if (password)
76 + await apiNewPassword(username, password)
77 + done(username)
78 + return alertDialog("Account modified", 'success')
79 + }
80 + }
81 + })
82 +}
83 +
84 +async function apiNewPassword(username: string, password: string) {
85 + const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
86 + const res = await createVerifierAndSalt(srp6aNimbusRoutines, username, password)
87 + return apiCall('change_srp_others', { username, salt: String(res.s), verifier: String(res.v) }).catch(e => {
88 + if (e.code !== 406) // 406 = server was configured to support clear text authentication
89 + throw e
90 + return apiCall('change_password_others', { username, newPassword: password }) // unencrypted version
91 + })
92 +}
admin/src/AccountsPage.ts
+4 -89
@@ -1,16 +1,15 @@
1 // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 -import { createElement as h, useState, useEffect, Fragment, useRef } from "react"
3 +import { createElement as h, useState, useEffect, Fragment } from "react"
4 import { apiCall, useApiEx } from './api'
5 import { Box, Button, Card, CardContent, Grid, List, ListItem, ListItemText, Typography } from '@mui/material'
6 import { Delete, Group, MilitaryTech, Person, PersonAdd, Refresh } from '@mui/icons-material'
7 -import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
7 import { alertDialog, confirmDialog } from './dialog'
9 -import { iconTooltip, isEqualLax, modifiedSx, onlyTruthy } from './misc'
8 +import { iconTooltip, onlyTruthy } from './misc'
9 import { TreeItem, TreeView } from '@mui/lab'
10 import { makeStyles } from '@mui/styles'
12 -import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
11 import MenuButton from './MenuButton'
12 +import AccountForm from './AccountForm'
13
14 const useStyles = makeStyles({
15 label: {
@@ -141,90 +140,6 @@ function hList(heading: string, list: any[]) {
140 )
141 }
142
144 -interface FormProps { account: Account, groups: string[], done: (username: string)=>void, close: ()=>void }
145 -function AccountForm({ account, done, groups, close }: FormProps) {
146 - const [values, setValues] = useState<Account & { password?: string, password2?: string }>(account)
147 - const [belongsOptions, setBelongOptions] = useState<string[]>([])
148 - useEffect(() => {
149 - setValues(account)
150 - setBelongOptions(groups.filter(x => x !== account.username ))
151 - ref.current?.querySelector('input')?.focus()
152 - }, [JSON.stringify(account)]) //eslint-disable-line
153 - const add = !account.username
154 - const group = !values.hasPassword
155 - const ref = useRef<HTMLFormElement>()
156 - return h(Form, {
157 - formRef: ref,
158 - values,
159 - set(v, k) {
160 - setValues({ ...values, [k]: v })
161 - },
162 - addToBar: [
163 - h(Button, { onClick: close, sx: { ml: 2 } }, "Close"),
164 - h(Box, { flex:1 }),
165 - account2icon(values, { fontSize: 'large', sx: { p: 1 }})
166 - ],
167 - fields: [
168 - { k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, xl: group ? 12 : 4,
169 - getError: v => v !== account.username && apiCall('get_account', { username: v }).then(() => "already used", () => false),
170 - },
171 - !group && { k: 'password', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', required: add,
172 - label: add ? "Password" : "Change password"
173 - },
174 - !group && { k: 'password2', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
175 - getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
176 - { k: 'ignore_limits', comp: BoolField, xl: 6,
177 - helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
178 - { k: 'admin', comp: BoolField, xl: 6, fromField: (v:boolean) => v||null, label: "Permission to access Admin interface",
179 - helperText: "It's THIS interface you are using right now.",
180 - ...account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited" },
181 - },
182 - { k: 'belongs', comp: MultiSelectField, label: "Inherits from", options: belongsOptions,
183 - helperText: "Specify groups to inherit permissions from."
184 - + (belongsOptions.length ? '' : " There are no groups available, create one first.")
185 - },
186 - { k: 'redirect', helperText: "If you want this account to be redirected to a specific folder/address at login time" },
187 - ],
188 - onError: alertDialog,
189 - save: {
190 - sx: modifiedSx( !isEqualLax(values, account)),
191 - async onClick() {
192 - const { password='', password2, adminActualAccess, ...withoutPassword } = values
193 - const { username } = values
194 - if (add) {
195 - await apiCall('add_account', withoutPassword)
196 - if (password)
197 - try { await apiNewPassword(username, password) }
198 - catch(e) {
199 - apiCall('del_account', { username }).then() // best effort, don't wait
200 - throw e
201 - }
202 - done(username)
203 - return alertDialog("Account created", 'success')
204 - }
205 - await apiCall('set_account', {
206 - username: account.username,
207 - changes: withoutPassword,
208 - })
209 - if (password)
210 - await apiNewPassword(username, password)
211 - done(username)
212 - return alertDialog("Account modified", 'success')
213 - }
214 - }
215 - })
216 -}
217 -
218 -function account2icon(ac: Account, props={}) {
143 +export function account2icon(ac: Account, props={}) {
144 return h(ac.hasPassword ? Person : Group, props)
145 }
221 -
222 -async function apiNewPassword(username: string, password: string) {
223 - const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
224 - const res = await createVerifierAndSalt(srp6aNimbusRoutines, username, password)
225 - return apiCall('change_srp_others', { username, salt: String(res.s), verifier: String(res.v) }).catch(e => {
226 - if (e.code !== 406) // 406 = server was configured to support clear text authentication
227 - throw e
228 - return apiCall('change_password_others', { username, newPassword: password }) // unencrypted version
229 - })
230 -}
todo.md
-1
@@ -5,7 +5,6 @@
5 - easier deploy on cloud server
6 - admin/fs: sort items
7 - plugin.api.subscribeConfig
8 -- watch certificates for change
8 - admin/fs: render virtual folders differently
9 - admin/config: hide advanced settings
10 - admin/fs: drag&drop to move items around