admin/plugins: show error if github is unreachable

Massimo Melina committed May 13, 2022 at 01:39 UTC ec2057155769c5d7fb5768a0bfd29f51f7e6fd1b
6 files changed +38 -19
admin/src/InstalledPlugins.ts
+8 -2
@@ -3,7 +3,7 @@ import { createElement as h, Fragment } from 'react'
3 import { Alert, Box, Tooltip } from '@mui/material'
4 import { DataGrid } from '@mui/x-data-grid'
5 import { Delete, Error, GitHub, PlayCircle, Settings, StopCircle, SystemUpdateAlt } from '@mui/icons-material'
6 -import { IconBtn } from './misc'
6 +import { IconBtn, xlate } from './misc'
7 import { formDialog, toast } from './dialog'
8 import _ from 'lodash'
9 import { BoolField, Field, MultiSelectField, NumberField, SelectField, StringField } from './Form'
@@ -12,7 +12,7 @@ import { ArrayField } from './ArrayField'
12 export default function InstalledPlugins({ updates }: { updates?: true }) {
13 const { list, setList, error, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
14 if (error)
15 - return h(Alert, { severity: 'error' }, error)
15 + return showError(error)
16 return h(DataGrid, {
17 rows: list.length ? list : [], // workaround for DataGrid bug causing 'no rows' message to be not displayed after 'loading' was also used
18 loading: initializing,
@@ -140,3 +140,9 @@ export function repoLink(repo?: string) {
140 link: 'https://github.com/' + repo,
141 })
142 }
143 +
144 +export function showError(error: any) {
145 + return h(Alert, { severity: 'error' }, xlate(error, {
146 + ENOTFOUND: "Couldn't reach github.com"
147 + }))
148 +}
admin/src/OnlinePlugins.ts
+2 -3
@@ -1,20 +1,19 @@
1 import { apiCall, useApiList } from './api'
2 import { Fragment, createElement as h, useState } from 'react'
3 -import { Alert } from '@mui/material'
3 import { DataGrid } from '@mui/x-data-grid'
4 import { IconBtn } from './misc'
5 import { Download, Search } from '@mui/icons-material'
6 import { toast } from './dialog'
7 import { StringField } from './Form'
8 import { useDebounce } from 'use-debounce'
10 -import { repoLink } from './InstalledPlugins'
9 +import { repoLink, showError } from './InstalledPlugins'
10
11 export default function OnlinePlugins() {
12 const [search, setSearch] = useState('')
13 const [debouncedSearch] = useDebounce(search, 1000)
14 const { list, error, initializing } = useApiList('search_online_plugins', { text: debouncedSearch })
15 if (error)
17 - return h(Alert, { severity: 'error' }, error)
16 + return showError(error)
17 return h(Fragment, {},
18 h(StringField, {
19 value: search,
admin/src/api.ts
+2
@@ -139,6 +139,8 @@ export function useApiList<T=any>(cmd:string|Falsy, params: Dict={}, { addId=fal
139 setInitializing(false)
140 return
141 }
142 + if (data.error)
143 + return setError(data.error)
144 if (data.add) {
145 const rec = map(data.add)
146 if (addId)
admin/src/misc.ts
+4
@@ -118,3 +118,7 @@ export function findFirst<I=any, O=any>(a: I[], cb:(v:I)=>O): any {
118 return ret
119 }
120 }
121 +
122 +export function xlate(input: any, table: Record<string, any>) {
123 + return table[input] ?? input
124 +}
server/src/api.plugins.ts
+19 -14
@@ -41,14 +41,18 @@ const apis: ApiHandlers = {
41 const list = sendList()
42 setTimeout(async () => {
43 const repo2id = getRepo2id()
44 - for (const repo in repo2id) {
45 - const online = await readOnlinePlugin(repo)
46 - if (!online.apiRequired || online.badApi) continue
47 - const id = repo2id[repo]
48 - const disk = getPluginInfo(id)
49 - if (online.version! > disk.version)
50 - list.add(online)
51 - }
44 + for (const repo in repo2id)
45 + try {
46 + const online = await readOnlinePlugin(repo)
47 + if (!online.apiRequired || online.badApi) continue
48 + const id = repo2id[repo]
49 + const disk = getPluginInfo(id)
50 + if (online.version! > disk.version)
51 + list.add(online)
52 + }
53 + catch (err:any) {
54 + list.error(err.code || err.message)
55 + }
56 list.end()
57 })
58 return list.return
@@ -108,6 +112,8 @@ const apis: ApiHandlers = {
112 }) )
113 }
114 list.end()
115 + }, (err: any) => {
116 + list.error(err.code || err.message)
117 })
118 return list.return
119 },
@@ -180,17 +186,16 @@ async function downloadPlugin(repo: string, overwrite?: boolean) {
186
187 export default apis
188
183 -function apiGithub(uri: string) {
184 - return httpsString('https://api.github.com/'+uri, {
189 +async function apiGithub(uri: string) {
190 + const res = await httpsString('https://api.github.com/'+uri, {
191 headers: {
192 'User-Agent': 'HFS',
193 Accept: 'application/vnd.github.v3+json',
194 }
189 - }).then(async res => {
190 - if (!res.ok)
191 - throw res.statusCode
192 - return JSON.parse(res.body)
195 })
196 + if (!res.ok)
197 + throw res.statusCode
198 + return JSON.parse(res.body)
199 }
200
201 function readOnlinePlugin(repo: string) {
server/src/apiMiddleware.ts
+3
@@ -78,6 +78,9 @@ export function sendList<T>(addAtStart?: T[]) {
78 end() { // notify end of additions
79 stream.push('end')
80 },
81 + error(msg: string) {
82 + stream.push({ error: msg })
83 + },
84 events(ctx: Koa.Context, eventMap: Parameters<typeof onOff>[1]) {
85 const off = onOff(events, eventMap)
86 ctx.res.once('close', off)