better code
Massimo Melina committed
Aug 23, 2023 at 11:15 UTC
80723385003446213aafcbf9cf8f0efdc1c4b69d
7 files changed
+36
-34
admin/src/FileForm.ts
+4
-13
@@ -15,17 +15,8 @@ import {
15
} from '@hfs/mui-grid-form'
16
import { apiCall, useApiEx } from './api'
17
import {
18
- basename,
19
- Btn,
20
- formatBytes,
21
- IconBtn,
22
- isEqualLax,
23
- modifiedSx,
24
- newDialog,
25
- objSameKeys,
26
- onlyTruthy,
27
- prefix,
28
- wikiLink
18
+ basename, Btn, formatBytes, IconBtn, isEqualLax, LinkBtn, modifiedSx, newDialog, objSameKeys,
19
+ onlyTruthy, prefix, wikiLink
20
} from './misc'
21
import { reloadVfs, VfsNode, VfsPerms, Who } from './VfsPage'
22
import md from './md'
@@ -214,8 +205,8 @@ function WhoField({ value, onChange, parent, inherit, accounts, helperText, show
205
}) ),
206
h(FormHelperText, {},
207
helperText,
217
- !isChildren && isDir && h(Link, {
218
- sx: { display: 'block', cursor: 'pointer', mt: -.5 },
208
+ !isChildren && isDir && h(LinkBtn, {
209
+ sx: { display: 'block', mt: -.5 },
210
onClick(event) {
211
if (thisValue === undefined) return
212
onChange(objectMode ? thisValue : { this: value }, { was: value, event })
admin/src/HomePage.ts
+2
-2
@@ -8,6 +8,7 @@ import {
8
dontBotherWithKeys,
9
Flex,
10
InLink,
11
+ LinkBtn,
12
objSameKeys,
13
onlyTruthy,
14
prefix,
@@ -59,8 +60,7 @@ export default function HomePage() {
60
const errors = serverErrors && onlyTruthy(Object.entries(serverErrors).map(([k,v]) =>
61
v && [md(`Protocol <u>${k}</u> cannot work: `), v,
62
(isCertError(v) || isKeyError(v)) && [
62
- SOLUTION_SEP, h(Link, {
63
- sx: { cursor: 'pointer' },
63
+ SOLUTION_SEP, h(LinkBtn, {
64
onClick() { makeCertAndSave().then(reloadCfg).then(reloadStatus) } },
65
"make one"
66
), " or ", SOLUTION_SEP, cfgLink("provide adequate files")
admin/src/InternetPage.ts
+15
-9
@@ -3,7 +3,7 @@ import { Alert, Box, Button, CircularProgress, LinearProgress, Link } from '@mui
3
import { HomeWorkTwoTone, PublicTwoTone, RouterTwoTone } from '@mui/icons-material'
4
import { apiCall, useApiEx } from './api'
5
import { closeDialog, with_ } from '@hfs/shared'
6
-import { Flex } from './misc'
6
+import { Flex, LinkBtn } from './misc'
7
import { alertDialog, confirmDialog, promptDialog, toast } from './dialog'
8
import { NumberField } from '@hfs/mui-grid-form'
9
import md from './md'
@@ -29,9 +29,15 @@ export default function InternetPage() {
29
setVerifyAgain(false)
30
verify().then()
31
}, [verifyAgain, nat, loading])
32
- return h(Box, {},
33
- h(Alert, { severity: 'info', sx: { mb: 2 } }, "This page helps you making your server work on the Internet"),
34
- error ? "Error" : !nat ? h(CircularProgress) : h(Flex, { justifyContent: 'space-around', alignItems: 'center', maxWidth: '40em' },
32
+ return h(Flex, { vert: true },
33
+ h(Alert, { severity: 'info' }, "This page helps you making your server work on the Internet"),
34
+ networkBox(),
35
+ )
36
+
37
+ function networkBox() {
38
+ if (error) return "Error"
39
+ if (!nat) return h(CircularProgress)
40
+ return h(Flex, { justifyContent: 'space-around', alignItems: 'center', maxWidth: '40em' },
41
h(Device, { name: "Local network", icon: HomeWorkTwoTone, color: localColor, ip: nat?.localIp,
42
below: port && h(Box, { fontSize: 'smaller' }, "port ", port),
43
}),
@@ -40,21 +46,21 @@ export default function InternetPage() {
46
name: "Router", icon: RouterTwoTone, ip: nat?.gatewayIp,
47
color: nat?.mapped && (wrongMap ? 'warning' : 'success'),
48
below: mapping ? h(LinearProgress, { sx: { height: '1em' } })
43
- : h(Link, { fontSize: 'smaller', display: 'block', onClick: configure, sx: { cursor: 'pointer' } },
49
+ : h(LinkBtn, { fontSize: 'smaller', display: 'block', onClick: configure },
50
"port ", wrongMap ? 'is wrong' : nat?.mapped ? nat.mapped.public.port : "unknown"),
51
}),
52
h(Sep),
53
h(Device, { name: "Internet", icon: PublicTwoTone, ip: nat?.publicIp,
54
color: checkResult ? 'success' : checkResult === false ? 'error' : doubleNat ? 'warning' : undefined,
55
below: checking ? h(LinearProgress, { sx: { height: '1em' } }) : h(Box, { fontSize: 'smaller' },
50
- doubleNat && h(Link, { sx: { cursor: 'pointer', display: 'block' }, onClick: () => alertDialog(MSG_ISP, 'warning') }, "Double NAT"),
56
+ doubleNat && h(LinkBtn, { display: 'block', onClick: () => alertDialog(MSG_ISP, 'warning') }, "Double NAT"),
57
checkResult ? "Working!" : checkResult === false ? "Failed!" : '',
58
' ',
53
- nat?.publicIp && h(Link, { onClick: verify, sx: { cursor: 'pointer' } }, "Verify")
59
+ nat?.publicIp && h(LinkBtn, { onClick: verify }, "Verify")
60
)
61
}),
56
- ),
57
- )
62
+ )
63
+ }
64
65
async function verify(): Promise<any> {
66
setCheckResult(undefined)
admin/src/OptionsPage.ts
+2
-2
@@ -5,7 +5,7 @@ import { createElement as h, Fragment, useEffect, useRef } from 'react';
5
import { apiCall, useApi, useApiEx } from './api'
6
import { state, useSnapState } from './state'
7
import { Info, Refresh, Warning } from '@mui/icons-material'
8
-import { Dict, Flex, iconTooltip, modifiedSx, wikiLink, with_ } from './misc'
8
+import { Dict, Flex, iconTooltip, LinkBtn, modifiedSx, wikiLink, with_ } from './misc'
9
import { Form, BoolField, NumberField, SelectField, FieldProps, Field, StringField } from '@hfs/mui-grid-form';
10
import { ArrayField } from './ArrayField'
11
import FileField from './FileField'
@@ -98,7 +98,7 @@ export default function OptionsPage() {
98
},
99
httpsEnabled && { k: 'cert', comp: FileField, md: 4, label: "HTTPS certificate file",
100
helperText: wikiLink('HTTPS#certificate', "What is this?"),
101
- error: with_(status?.https.error, e => isCertError(e) && [e, ' - ', h(Link, { key: 'fix', sx: { cursor: 'pointer' }, onClick: makeCertAndSave }, "make one")]),
101
+ error: with_(status?.https.error, e => isCertError(e) && [e, ' - ', h(LinkBtn, { key: 'fix', onClick: makeCertAndSave }, "make one")]),
102
},
103
httpsEnabled && { k: 'private_key', comp: FileField, md: 4, label: "HTTPS private key file",
104
...with_(status?.https.error, e => isKeyError(e) ? { error: true, helperText: e } : null)
admin/src/misc.ts
+7
-2
@@ -8,7 +8,7 @@ import {
8
CircularProgress,
9
IconButton,
10
IconButtonProps,
11
- Link,
11
+ Link, LinkProps,
12
Tooltip, TooltipProps,
13
useMediaQuery
14
} from '@mui/material'
@@ -133,6 +133,10 @@ export function InLink(props:any) {
133
export const Center = forwardRef((props: BoxProps, ref) =>
134
h(Box, { ref, display:'flex', height:'100%', width:'100%', justifyContent:'center', alignItems:'center', flexDirection: 'column', ...props }))
135
136
+export function LinkBtn({ ...rest }: LinkProps) {
137
+ return h(Link, { ...rest, sx: { cursor: 'pointer', ...rest.sx } })
138
+}
139
+
140
export async function manipulateConfig(k: string, work:(data:any) => any) {
141
const cfg = await apiCall('get_config', { only: [k] })
142
const was = cfg[k]
@@ -199,12 +203,13 @@ export function IconProgress({ icon, progress, offset, addTitle, sx }: IconProgr
203
)
204
}
205
202
-export function Flex({ gap='.8em', vert=false, children=null, props={}, ...rest }) {
206
+export function Flex({ gap='.8em', vert=false, center=false, children=null, props={}, ...rest }) {
207
return h(Box, {
208
sx: {
209
display: 'flex',
210
gap,
211
flexDirection: vert ? 'column' : undefined,
212
+ ...center && { alignItems: 'center', justifyContent: 'center' },
213
...rest,
214
},
215
...props
dev.md
+1
@@ -38,6 +38,7 @@ Alternatively you can run a development server, just be sure to load config from
38
- All objects that go in yaml should use snake_case.
39
- Reason: we want something that is both easy for the user and maps directly in our code.
40
Spaces and kebab-case don't play well with javascript and camel is less readable for the user.
41
+- API names should start with get_ if and only if they provide information without making changes.
42
43
# Project design
44
src/api.file_list.ts
+5
-6
@@ -20,17 +20,16 @@ import { HTTP_FOOL, HTTP_METHOD_NOT_ALLOWED, HTTP_NOT_FOUND } from './const'
20
import Koa from 'koa'
21
22
export const get_file_list: ApiHandler = async ({ uri, offset, limit, search, c }, ctx) => {
23
- const node = await urlToNode( uri || '/', ctx)
24
- const list = new SendListReadable()
23
+ const node = await urlToNode(uri || '/', ctx)
24
+ const list = ctx.get('accept') === 'text/event-stream' ? new SendListReadable() : undefined
25
if (!node)
26
return fail(HTTP_NOT_FOUND)
27
if (statusCodeForMissingPerm(node,'can_list',ctx))
28
return fail()
29
if (dirTraversal(search))
30
return fail(HTTP_FOOL)
31
- const sse = ctx.get('accept') === 'text/event-stream'
31
if (node.default)
33
- return (sse ? list.custom : _.identity)({ // sse will wrap the object in a 'custom' message, otherwise we plainly return the object
32
+ return (list?.custom ?? _.identity)({ // sse will wrap the object in a 'custom' message, otherwise we plainly return the object
33
redirect: uri // tell the browser to access the folder (instead of using this api), so it will get the default file
34
})
35
if (!await nodeIsDirectory(node))
@@ -43,7 +42,7 @@ export const get_file_list: ApiHandler = async ({ uri, offset, limit, search, c
42
const can_upload = hasPermission(node, 'can_upload', ctx)
43
const can_delete = hasPermission(node, 'can_delete', ctx)
44
const props = { can_upload, can_delete, accept: node.accept }
46
- if (!sse)
45
+ if (!list)
46
return { ...props, list: await asyncGeneratorToArray(produceEntries()) }
47
setTimeout(async () => {
48
if (can_upload || can_delete)
@@ -55,7 +54,7 @@ export const get_file_list: ApiHandler = async ({ uri, offset, limit, search, c
54
return list
55
56
function fail(code=ctx.status) {
58
- if (!sse)
57
+ if (!list)
58
return new ApiError(code)
59
list.error(code, true)
60
return list