ux: better certificate dialog
Massimo Melina committed
May 6, 2023 at 14:56 UTC
38dbbb92da920aa5490edad790ef6703f7303ead
3 files changed
+38
-32
admin/src/OptionsPage.ts
+31
-28
@@ -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, modifiedSx, wikiLink, with_ } from './misc'
8
+import { Dict, Flex, modifiedSx, wikiLink, with_ } from './misc'
9
import {
10
Form,
11
BoolField,
@@ -17,7 +17,7 @@ import {
17
StringField
18
} from '@hfs/mui-grid-form';
19
import FileField from './FileField'
20
-import { alertDialog, closeDialog, confirmDialog, formDialog, newDialog, toast, waitDialog } from './dialog'
20
+import { alertDialog, closeDialog, confirmDialog, newDialog, toast } from './dialog'
21
import { proxyWarning } from './HomePage'
22
import _ from 'lodash';
23
import { proxy, subscribe, useSnapshot } from 'valtio'
@@ -307,33 +307,36 @@ function suggestMakingCert() {
307
export async function makeCertAndSave() {
308
if (!window.crypto.subtle)
309
return alertDialog("Retry this procedure on localhost", 'warning')
310
- const res = await formDialog<{ commonName: string }>({
311
- title: "We'll generate a basic certificate for you",
312
- form: {
313
- fields: [
314
- h(Box, { display: 'flex', gap: 1, alignItems: 'center' },
315
- h(Warning), "This certificate is just one click away, but will issue a warning on the browser"),
316
- { k: 'commonName', label: "Enter a domain, or leave empty" }
317
- ],
318
- save: { children: "Continue" },
319
- barSx: { gap: 1 },
320
- addToBar: [
321
- h(Link, { target: 'cert', href: 'https://letsencrypt.org/' }, h(Button, {}, "Get better certificate"))
322
- ],
323
- }
310
+ const close = newDialog({
311
+ title: "Get a certificate",
312
+ Content: () => h(Flex, { flexDirection: 'column' },
313
+ h('p', {}, "HTTPS needs a certificate to work."),
314
+ "We suggest you to ",
315
+ h(Link, {
316
+ target: 'cert',
317
+ href: 'https://letsencrypt.org/',
318
+ onClick: close,
319
+ }, h(Button, { size: 'small', color: 'success' }, "get a free but proper certificate")),
320
+ "or, if you are in a hurry",
321
+ h(Button, {
322
+ size: 'small',
323
+ color: 'warning',
324
+ async onClick() {
325
+ try {
326
+ const saved = await apiCall('save_pem', await makeCert({}))
327
+ await apiCall('set_config', { values: saved })
328
+ if (loaded) // when undefined we are not in this page
329
+ Object.assign(loaded, saved)
330
+ setTimeout(exposedReloadStatus!, 1000) // give some time for backend to apply
331
+ Object.assign(state.config, saved)
332
+ await alertDialog("Certificate saved", 'success')
333
+ }
334
+ finally { close() }
335
+ }
336
+ }, "make a basic certificate"),
337
+ wikiLink('HTTPS#certificate', h(Flex, {}, h(Warning, { color: 'warning' }), "but BEWARE it won't be perfect"))
338
+ )
339
})
325
- if (!res) return
326
- const close = waitDialog()
327
- try {
328
- const saved = await apiCall('save_pem', await makeCert(res))
329
- await apiCall('set_config', { values: saved })
330
- if (loaded) // when undefined we are not in this page
331
- Object.assign(loaded, saved)
332
- setTimeout(exposedReloadStatus!, 1000) // give some time for backend to apply
333
- Object.assign(state.config, saved)
334
- await alertDialog("Certificate saved", 'success')
335
- }
336
- finally { close() }
340
}
341
342
async function makeCert(attributes: Record<string, string>) {
admin/src/dialog.ts
+4
-3
@@ -12,7 +12,7 @@ import {
12
import {
13
createElement as h, Dispatch, Fragment,
14
isValidElement,
15
- ReactElement, SetStateAction,
15
+ ReactElement, ReactNode, SetStateAction,
16
useEffect,
17
useRef,
18
useState
@@ -117,12 +117,13 @@ export async function alertDialog(msg: ReactElement | string | Error, options?:
117
})
118
}
119
120
-interface ConfirmOptions { href?: string }
121
-export async function confirmDialog(msg: string | ReactElement, { href }: ConfirmOptions={}) : Promise<boolean> {
120
+interface ConfirmOptions extends Omit<DialogOptions, 'Content'> { href?: string }
121
+export async function confirmDialog(msg: ReactNode, { href, ...rest }: ConfirmOptions={}) : Promise<boolean> {
122
return new Promise(resolve => newDialog({
123
className: 'dialog-confirm',
124
icon: '?',
125
onClose: resolve,
126
+ ...rest,
127
Content
128
}) )
129
admin/src/misc.ts
+3
-1
@@ -17,7 +17,7 @@ import { SxProps } from '@mui/system'
17
import { Refresh, SvgIconComponent } from '@mui/icons-material'
18
import { alertDialog, confirmDialog } from './dialog'
19
import { apiCall } from './api'
20
-import { formatPerc, useStateMounted } from '@hfs/shared'
20
+import { dontBotherWithKeys, formatPerc, useStateMounted } from '@hfs/shared'
21
import { Promisable } from '@hfs/mui-grid-form'
22
import { LoadingButton, LoadingButtonProps } from '@mui/lab'
23
export * from '@hfs/shared'
@@ -203,5 +203,7 @@ export const REPO_URL = 'https://github.com/rejetto/hfs/'
203
export const WIKI_URL = REPO_URL + '/wiki/'
204
205
export function wikiLink(uri: string, content: ReactNode) {
206
+ if (Array.isArray(content))
207
+ content = dontBotherWithKeys(content)
208
return h(Link, { href: WIKI_URL + uri, target: 'help' }, content)
209
}
\ No newline at end of file