fix: admin/home: wrong instructions displayed in case of network error
Massimo Melina committed
Apr 8, 2022 at 14:59 UTC
a6e9db009cfaf79bb05424cc56bcec2c1f6c5d32
6 files changed
+47
-42
admin/src/AccountsPage.ts
+1
-1
@@ -21,7 +21,7 @@ const useStyles = makeStyles({
21
}
22
})
23
24
-interface Account {
24
+export interface Account {
25
username: string
26
hasPassword?: boolean
27
adminActualAccess?: boolean
admin/src/ConfigPage.ts
+3
-1
@@ -25,7 +25,7 @@ export const logLabels = {
25
export default function ConfigPage() {
26
const [res, reloadConfig] = useApiComp('get_config', { omit: ['vfs'] })
27
let snap = useSnapState()
28
- const [status, reloadStatus] = useApi(res && 'get_status')
28
+ const [status, reloadStatus] = useApiComp(res && 'get_status')
29
useEffect(reloadStatus, [res, reloadStatus])
30
31
exposedReloadStatus = reloadStatus
@@ -33,6 +33,8 @@ export default function ConfigPage() {
33
34
if (isValidElement(res))
35
return res
36
+ if (isValidElement(status))
37
+ return status
38
const { changes } = snap
39
const values = (loaded !== res) ? (state.config = loaded = res) : snap.config
40
const maxSpeedDefaults = {
admin/src/FileCard.ts
+7
-4
@@ -1,10 +1,10 @@
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 { state, useSnapState } from './state'
4
-import { createElement as h, useEffect, useMemo, useState } from 'react'
4
+import { createElement as h, isValidElement, useEffect, useMemo, useState } from 'react'
5
import { Alert, Button, Card, CardContent, List, ListItem, ListItemText } from '@mui/material'
6
import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, SelectField } from './Form'
7
-import { apiCall, useApi } from './api'
7
+import { apiCall, useApiComp } from './api'
8
import { formatBytes, isEqualLax, modifiedSx, onlyTruthy } from './misc'
9
import { reloadVfs, Who } from './VfsPage'
10
import md from './md'
@@ -34,8 +34,6 @@ function FileForm({ file }: { file: ReturnType<typeof useSnapState>['selectedFil
34
setValues(Object.assign({ can_see: null, can_read: null }, rest))
35
}, [file]) //eslint-disable-line
36
37
- const accounts = useApi('get_accounts')[0]?.list
38
-
37
const { source } = file
38
const isDir = file.type === 'folder'
39
const hasSource = source !== undefined // we need a boolean
@@ -56,6 +54,11 @@ function FileForm({ file }: { file: ReturnType<typeof useSnapState>['selectedFil
54
const showCanSee = (values.can_read ?? inheritedPerms.can_read) === true
55
const showTimestamps = hasSource && Boolean(values.ctime)
56
57
+ let [accountsRes] = useApiComp<{ list: Account[] }>('get_accounts')
58
+ if (isValidElement(accountsRes))
59
+ return accountsRes
60
+ const accounts = accountsRes.list
61
+
62
return h(Form, {
63
values,
64
set(v, k) {
admin/src/HomePage.ts
+20
-24
@@ -1,26 +1,28 @@
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, Fragment } from 'react'
3
+import { createElement as h, isValidElement } from 'react'
4
import { Box, Button, Link } from '@mui/material'
5
-import { apiCall, useApi } from './api'
6
-import { Dict, dontBotherWithKeys, InLink, objSameKeys, onlyTruthy, spinner } from './misc'
5
+import { apiCall, useApi, useApiComp } from './api'
6
+import { Dict, dontBotherWithKeys, InLink, objSameKeys, onlyTruthy } from './misc'
7
import { CheckCircle, Error, Info, Launch, Warning } from '@mui/icons-material'
8
import md from './md'
9
import { useSnapState } from './state'
10
import { confirmDialog } from './dialog'
11
import { isCertError, makeCertAndSave } from './ConfigPage'
12
+import { VfsNode } from './VfsPage'
13
+import { Account } from './AccountsPage'
14
15
interface ServerStatus { listening: boolean, port: number, error?: string, busy?: string }
16
17
export default function HomePage() {
18
const SOLUTION_SEP = " — "
19
const { username } = useSnapState()
18
- const [status, reloadStatus] = useApi<Dict<ServerStatus>>('get_status')
19
- const [vfs] = useApi('get_vfs')
20
- const [account] = useApi(username && 'get_account')
21
- const [cfg, reloadCfg] = useApi('get_config', { only: ['https_port', 'cert', 'private_key', 'proxies', 'ignore_proxies'] })
22
- if (!status)
23
- return spinner()
20
+ const [status, reloadStatus] = useApiComp<Dict<ServerStatus>>('get_status')
21
+ const [vfs] = useApiComp<{ root?: VfsNode }>('get_vfs')
22
+ const [account] = useApi<Account>(username && 'get_account')
23
+ const [cfg, reloadCfg] = useApiComp('get_config', { only: ['https_port', 'cert', 'private_key', 'proxies', 'ignore_proxies'] })
24
+ if (!status || isValidElement(status))
25
+ return status
26
const { http, https } = status
27
const goSecure = !http?.listening && https?.listening ? 's' : ''
28
const srv = goSecure ? https : (http?.listening && http)
@@ -37,21 +39,15 @@ export default function HomePage() {
39
]]))
40
return h(Box, { display:'flex', gap: 2, flexDirection:'column' },
41
username && entry('', "Welcome "+username),
40
- !cfg ? spinner() :
41
- errors.length ? dontBotherWithKeys(errors.map(msg => entry('error', dontBotherWithKeys(msg))))
42
- : entry('success', "Server is working"),
43
- !vfs ? spinner()
44
- : !vfs.root?.children?.length && !vfs.root?.source
45
- ? entry('warning', "You have no files shared", SOLUTION_SEP, fsLink("add some"))
46
- : entry('', md("Here you manage your server. There is a _separated_ interface to access your shared files: "),
47
- h(Link, { target:'frontend', href: '/' }, "Frontend interface", h(Launch, { sx: { verticalAlign: 'sub', ml: '.2em' } }))),
48
- ! href && entry('warning', "Frontend unreachable: ",
49
- !cfg ? '...'
50
- : errors.length === 2 ? "both http and https are in error"
51
- : h(Fragment, {},
52
- ['http','https'].map(k => k + " " + (errorMap[k] ? "is in error" : "is off")).join(', '),
53
- !errors.length && h(Fragment, {}, SOLUTION_SEP, cfgLink("switch http or https on"))
54
- )
42
+ errors.length ? dontBotherWithKeys(errors.map(msg => entry('error', dontBotherWithKeys(msg))))
43
+ : entry('success', "Server is working"),
44
+ !vfs || isValidElement(vfs) ? vfs
45
+ : !vfs.root?.children?.length && !vfs.root?.source ? entry('warning', "You have no files shared", SOLUTION_SEP, fsLink("add some"))
46
+ : entry('', md("Here you manage your server. There is a _separated_ interface to access your shared files: "),
47
+ h(Link, { target:'frontend', href: '/' }, "Frontend interface", h(Launch, { sx: { verticalAlign: 'sub', ml: '.2em' } }))),
48
+ !href && entry('warning', "Frontend unreachable: ",
49
+ ['http','https'].map(k => k + " " + (errorMap[k] ? "is in error" : "is off")).join(', '),
50
+ !errors.length && [ SOLUTION_SEP, cfgLink("switch http or https on") ]
51
),
52
!account?.adminActualAccess && entry('', md("You are accessing on _localhost_ where permission is not required"),
53
SOLUTION_SEP, h(InLink, { to:'accounts' }, "give admin access to an account to be able to access from other computers") ),
admin/src/LogoutPage.ts
+5
-4
@@ -1,13 +1,14 @@
1
-import { createElement as h } from "react"
1
+import { createElement as h, isValidElement } from "react"
2
import { Alert, Box, Button } from '@mui/material'
3
-import { apiCall, useApi } from './api'
3
+import { apiCall, useApiComp } from './api'
4
import { alertDialog } from "./dialog"
5
import { useSnapState } from './state'
6
7
export default function LogoutPage() {
8
- const [cfg] = useApi('get_config', { only: [] })
8
+ const [cfg] = useApiComp('get_config', { only: [] })
9
const { username } = useSnapState()
10
- if (!cfg) return null
10
+ if (isValidElement(cfg))
11
+ return cfg
12
if (!username)
13
return h(Alert, { severity: 'info' }, "You are not logged in, because authentication is not required on localhost")
14
return h(Box, { display: 'flex', flexDirection:'column', gap: 2 },
admin/src/api.ts
+11
-8
@@ -1,6 +1,6 @@
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, ReactElement, useCallback, useEffect, useMemo, useRef } from 'react'
3
+import { createElement as h, ReactElement, Fragment, useCallback, useEffect, useMemo, useRef } from 'react'
4
import { Dict, Falsy, getCookie, IconBtn, spinner, useStateMounted } from './misc'
5
import { Alert } from '@mui/material'
6
import _ from 'lodash'
@@ -8,11 +8,12 @@ import { state } from './state'
8
import { Refresh } from '@mui/icons-material'
9
10
export function useApiComp<T=any>(...args: Parameters<typeof useApi>): [T | ReactElement, ()=>void] {
11
- const [res, reload] = useApi<T>(...args)
11
+ const [res, err, reload] = useApi<T>(...args)
12
return useMemo(() =>
13
- res === undefined ? [spinner(), reload]
14
- : res && res instanceof Error ? [ h(Alert, { severity: 'error' }, String(res), h(IconBtn, { icon: Refresh, onClick: reload, sx: { m:'-8px 0 -8px 16px' } })), reload ]
15
- : [res, reload],
13
+ !args[0] ? [h(Fragment), reload]
14
+ : err ? [ h(Alert, { severity: 'error' }, String(err), h(IconBtn, { icon: Refresh, onClick: reload, sx: { m:'-8px 0 -8px 16px' } })), reload ]
15
+ : res === undefined ? [spinner(), reload]
16
+ : [res, reload],
17
[res, reload])
18
}
19
@@ -46,20 +47,22 @@ export class ApiError extends Error {
47
}
48
}
49
49
-export function useApi<T=any>(cmd: string | Falsy, params?: object) : [T | undefined, ()=>void] {
50
+export function useApi<T=any>(cmd: string | Falsy, params?: object) : [T | undefined, undefined | Error, ()=>void] {
51
const [ret, setRet] = useStateMounted<T | undefined>(undefined)
52
+ const [err, setErr] = useStateMounted<Error | undefined>(undefined)
53
const [forcer, setForcer] = useStateMounted(0)
54
const loadingRef = useRef(false)
55
useEffect(()=>{
56
setRet(undefined)
57
+ setErr(undefined)
58
if (!cmd) return
59
loadingRef.current = true
60
apiCall(cmd, params)
58
- .then(setRet, setRet)
61
+ .then(setRet, setErr)
62
.finally(()=> loadingRef.current = false)
63
}, [cmd, JSON.stringify(params), forcer]) //eslint-disable-line -- json-ize to detect deep changes
64
const reload = useCallback(()=> loadingRef.current || setForcer(v => v+1), [setForcer])
62
- return [ret, reload]
65
+ return [ret, err, reload]
66
}
67
68
type EventHandler = (type:string, data?:any) => void