admin/config: helper text for server status
Massimo Melina committed
Feb 13, 2022 at 19:56 UTC
7b4792657f4ef7695c7ced4af24bcb8f7360b402
5 files changed
+63
-56
admin/src/ConfigPage.ts
+33
-21
@@ -1,6 +1,6 @@
1
-import { Box, Button } from '@mui/material';
2
-import { createElement as h, isValidElement, useRef } from 'react';
3
-import { apiCall, useApiComp } from './api'
1
+import { Box, Button, FormHelperText } from '@mui/material';
2
+import { createElement as h, isValidElement, useEffect, useRef } from 'react';
3
+import { apiCall, useApi, useApiComp } from './api'
4
import { state, useSnapState } from './state'
5
import { Refresh } from '@mui/icons-material'
6
import { Dict } from './misc'
@@ -14,10 +14,12 @@ let loaded: Dict | undefined
14
subscribeKey(state, 'config', recalculateChanges)
15
16
export default function ConfigPage() {
17
- const [res, reload] = useApiComp('get_config', {
17
+ const [res, reloadConfig] = useApiComp('get_config', {
18
omit: ['vfs', 'accounts']
19
})
20
let snap = useSnapState()
21
+ const [status, reloadStatus] = useApi(res && 'get_status')
22
+ useEffect(reloadStatus, [res])
23
if (isValidElement(res))
24
return res
25
const { changes } = snap
@@ -35,7 +37,10 @@ export default function ConfigPage() {
37
disabled: !Object.keys(changes).length,
38
},
39
addToBar: [h(Button, {
38
- onClick: reload,
40
+ onClick() {
41
+ reloadConfig()
42
+ reloadStatus()
43
+ },
44
startIcon: h(Refresh),
45
}, 'Reload')],
46
defaults({ comp }) {
@@ -43,8 +48,8 @@ export default function ConfigPage() {
48
return { md: shortField ? 3 : 6 }
49
},
50
fields: [
46
- { k: 'port', comp: ServerPort, label:'HTTP port' },
47
- { k: 'https_port', comp: ServerPort, label: 'HTTPS port' },
51
+ { k: 'port', comp: ServerPort, label:'HTTP port', status: status?.http||true },
52
+ { k: 'https_port', comp: ServerPort, label: 'HTTPS port', status: status?.https||true },
53
config.https_port >= 0 && { k: 'cert', comp: StringField, label: 'HTTPS certificate file' },
54
config.https_port >= 0 && { k: 'private_key', comp: StringField, label: 'HTTPS private key file' },
55
{ k: 'admin_port', comp: ServerPort, label: 'Admin port' },
@@ -71,6 +76,7 @@ export default function ConfigPage() {
76
77
async function save() {
78
await apiCall('set_config', { values: state.changes })
79
+ setTimeout(reloadStatus, 1000)
80
Object.assign(loaded, state.changes) // since changes are recalculated subscribing state.config, but it depends on 'loaded' to (which cannot be subscribed), be sure to update loaded first
81
recalculateChanges()
82
await alertDialog("Changes applied", 'success')
@@ -86,23 +92,29 @@ function recalculateChanges() {
92
state.changes = changes
93
}
94
89
-function ServerPort({ label, value, onChange }: FieldProps<number | null>) {
95
+function ServerPort({ label, value, onChange, status }: FieldProps<number | null>) {
96
const lastCustom = useRef(1)
97
if (value! > 0)
98
lastCustom.current = value!
99
const selectValue = Number(value! > 0 ? lastCustom.current : value) || 0
94
- return h(Box, { display:'flex' },
95
- h(SelectField as Field<number>, {
96
- sx: { flexGrow: 1 },
97
- label,
98
- value: selectValue,
99
- options: [
100
- { label: 'off', value: -1 },
101
- { label: 'automatic port', value: 0 },
102
- { label: 'choose port number', value: lastCustom.current },
103
- ],
104
- onChange,
105
- }),
106
- value! > 0 && h(NumberField, { label: 'Number', fullWidth: false, value, onChange }),
100
+ const error = status?.error
101
+ return h(Box, {},
102
+ h(Box, { display:'flex' },
103
+ h(SelectField as Field<number>, {
104
+ sx: { flexGrow: 1 },
105
+ label,
106
+ value: selectValue,
107
+ options: [
108
+ { label: 'off', value: -1 },
109
+ { label: 'automatic port', value: 0 },
110
+ { label: 'choose port number', value: lastCustom.current },
111
+ ],
112
+ onChange,
113
+ }),
114
+ value! > 0 && h(NumberField, { label: 'Number', fullWidth: false, value, onChange }),
115
+ ),
116
+ status && h(FormHelperText, { error: Boolean(error) },
117
+ status === true ? '...'
118
+ : error ?? (status?.listening && 'working on port '+ status.port) )
119
)
120
}
admin/src/HomePage.ts
-2
@@ -20,8 +20,6 @@ export default function HomePage() {
20
const errorMap = objSameKeys(status, v =>
21
v.busy ? [`port ${v.port} already used by ${v.busy} - choose a `, cfgLink('different port'), ` or stop ${v.busy}`]
22
: v.error )
23
- if (!errorMap.https && cfg?.https_port >= 0 && !status.https.listening)
24
- errorMap.https = !cfg.cert ? 'missing certificate' : !cfg.private_key ? 'missing private key' : ''
23
const errors = errorMap && onlyTruthy(Object.entries(errorMap).map(([k,v]) =>
24
v && [md(`Protocol _${k}_ cannot work: `), v, typeof v === 'string' && /certificate|key/.test(v) && [' - ', cfgLink("provide adequate files")]]))
25
return h(Box, { display:'flex', gap: 2, flexDirection:'column' },
src/adminApis.ts
+1
-1
@@ -37,7 +37,7 @@ export const adminApis: ApiHandlers = {
37
function serverStatus(h: typeof st.httpSrv, configuredPort?: number) {
38
return {
39
..._.pick(h, ['listening', 'busy', 'error']),
40
- port: (h.address() as any)?.port || configuredPort,
40
+ port: (h?.address() as any)?.port || configuredPort,
41
}
42
}
43
},
src/listen.ts
+28
-32
@@ -6,15 +6,15 @@ import { watchLoad } from './watchLoad'
6
import { networkInterfaces } from 'os';
7
import { newConnection } from './connections'
8
import open from 'open'
9
-import { debounceAsync } from './misc'
9
+import { debounceAsync, prefix } from './misc'
10
import { DEV } from './const'
11
import findProcess from 'find-process'
12
+import _ from 'lodash'
13
14
interface ServerExtra { error?: string, busy?: string }
15
let httpSrv: http.Server & ServerExtra
16
let httpsSrv: http.Server & ServerExtra
17
let adminSrv: http.Server & ServerExtra
17
-let cert:string, key: string
18
19
subscribeConfig<number>({ k:'port', defaultValue: 80 }, async port => {
20
await stopServer(httpSrv)
@@ -50,50 +50,46 @@ defineConfig('open_browser_at_start', { defaultValue: !DEV })
50
subscribeConfig<string>({ k:'admin_network', defaultValue: '127.0.0.1' }, considerAdmin)
51
subscribeConfig<number>({ k:'admin_port', defaultValue: 63636 }, considerAdmin)
52
53
-subscribeConfig({ k:'cert' }, async (v: string) => {
54
- await stopServer(httpsSrv)
55
- cert = v
56
- if (!cert) return
57
- if (cert.includes('\n'))
58
- return considerHttps()
59
- // it's a path
60
- watchLoad(cert, data => {
61
- cert = data
62
- considerHttps()
53
+const httpsNeeds = { cert:'', private_key:'' }
54
+const httpsNeedsNames = { cert: 'certificate', private_key: 'private key' }
55
+for (const k of Object.keys(httpsNeeds) as (keyof typeof httpsNeeds)[]) { // please be smarter typescript
56
+ let unwatch: ReturnType<typeof watchLoad>['unwatch']
57
+ subscribeConfig({ k }, async (v: string) => {
58
+ unwatch?.()
59
+ httpsNeeds[k] = v
60
+ if (!v || v.includes('\n'))
61
+ return considerHttps()
62
+ // it's a path
63
+ httpsNeeds[k] = ''
64
+ unwatch = watchLoad(v, data => {
65
+ httpsNeeds[k] = data
66
+ considerHttps()
67
+ }).unwatch
68
+ await considerHttps()
69
})
64
- cert = ''
65
-})
66
-
67
-subscribeConfig({ k:'private_key' }, async (v: string) => {
68
- await stopServer(httpsSrv)
69
- key = v
70
- if (!key) return
71
- if (key.includes('\n'))
72
- return considerHttps()
73
- // it's a path
74
- watchLoad(key, data => {
75
- key = data
76
- considerHttps()
77
- })
78
- key = ''
79
-})
70
+}
71
72
const CFG_HTTPS_PORT = 'https_port'
73
subscribeConfig({ k:CFG_HTTPS_PORT, defaultValue: -1 }, considerHttps)
74
75
async function considerHttps() {
76
await stopServer(httpsSrv)
77
+ let port = getConfig('https_port')
78
try {
87
- httpsSrv = https.createServer({ key, cert }, app.callback())
88
- httpsSrv.error = undefined
79
+ httpsSrv = https.createServer({ key: httpsNeeds.private_key, cert: httpsNeeds.cert }, app.callback())
80
+ const missingKey = _.findKey(httpsNeeds, v => !v) as keyof typeof httpsNeeds
81
+ httpsSrv.error = port < 0 ? undefined
82
+ : missingKey && prefix(getConfig(missingKey) ? "cannot read file for " : "missing ", httpsNeedsNames[missingKey])
83
+ if (httpsSrv.error)
84
+ return
85
}
86
catch(e) {
87
httpsSrv.error = "bad private key or certificate"
88
console.log("failed to create https server: check your private key and certificate", String(e))
89
return
90
}
95
- const port = await startServer(httpsSrv, {
96
- port: !cert || !key ? -1 : getConfig('https_port'),
91
+ port = await startServer(httpsSrv, {
92
+ port: getConfig('https_port'),
93
name: 'https'
94
})
95
if (!port) return
todo.md
+1
@@ -1,4 +1,5 @@
1
# To do
2
+- admin/config: use filepicker for https files
3
- admin: warn in case of items with same name
4
- password protect admin
5
- allowed referer