admin/plugins: install dependencies automatically

Massimo Melina committed Oct 7, 2023 at 20:31 UTC 3a0d0f0b39d50f98d67ee57b467cda61ae7dcb1a
4 files changed +63 -44
admin/src/OnlinePlugins.ts
+28 -11
@@ -3,13 +3,13 @@
3 import { apiCall, useApiList } from './api'
4 import { Fragment, createElement as h, useState } from 'react'
5 import { DataTable } from './DataTable'
6 -import { IconBtn } from './misc'
6 +import { HTTP_FAILED_DEPENDENCY, IconBtn } from './misc'
7 import { Download, Search } from '@mui/icons-material'
8 import { StringField } from '@hfs/mui-grid-form'
9 import { useDebounce } from 'usehooks-ts'
10 import { renderName, startPlugin } from './InstalledPlugins'
11 import { state, useSnapState } from './state'
12 -import { alertDialog, confirmDialog } from './dialog'
12 +import { alertDialog, confirmDialog, toast } from './dialog'
13
14 export default function OnlinePlugins() {
15 const [search, setSearch] = useState('')
@@ -73,23 +73,40 @@ export default function OnlinePlugins() {
73 disabled: row.installed && "Already installed",
74 tooltipProps: { placement:'bottom-end' }, // workaround problem with horizontal scrolling by moving the tooltip leftward
75 confirm: "WARNING - Proceed only if you trust this author and this plugin",
76 - async onClick() {
76 + onClick() {
77 const branch = row.branch || row.default_branch
78 - try {
79 - const res = await apiCall('download_plugin', { id, branch }, { timeout: false })
80 - if (await confirmDialog(`Plugin ${id} downloaded`, { confirmText: "Start" }))
81 - await startPlugin(res.id)
82 - }
83 - catch(e: any) {
84 - if (e.code !== 424) throw e
78 + installPlugin(id, branch).catch((e: any) => {
79 + if (e.code !== HTTP_FAILED_DEPENDENCY)
80 + return alertDialog(e)
81 const msg = h(Fragment, {}, "This plugin has some dependencies unmet:",
82 e.data.map((x: any) => h('li', { key: x.repo }, x.repo + ': ' + x.error)) )
83 return alertDialog(msg, 'error')
88 - }
84 + })
85 }
86 })
87 ]
88 })
89 )
90 +
91 + async function installPlugin(id: string, branch?: string): Promise<any> {
92 + try {
93 + const res = await apiCall('download_plugin', { id, branch }, { timeout: false })
94 + if (await confirmDialog(`Plugin ${id} downloaded`, { confirmText: "Start" }))
95 + await startPlugin(res.id)
96 + }
97 + catch(e:any) {
98 + let done = false
99 + if (e.code === HTTP_FAILED_DEPENDENCY) // try to install automatically
100 + for (const x of e.cause)
101 + if (x.error === 'missing') {
102 + toast("Installing dependency: " + x.repo)
103 + await installPlugin(x.repo)
104 + done = true
105 + }
106 + if (done) // try again
107 + return installPlugin(id, branch)
108 + throw e
109 + }
110 + }
111 }
112
src/const.ts
+1 -33
@@ -5,6 +5,7 @@ import * as fs from 'fs'
5 import { homedir } from 'os'
6 import { mkdirSync } from 'fs'
7 import { basename, dirname, join } from 'path'
8 +export * from './cross-const'
9
10 export const argv = minimist(process.argv.slice(2))
11 export const DEV = process.env.DEV || argv.dev ? 'DEV' : ''
@@ -15,40 +16,7 @@ export const BUILD_TIMESTAMP = fs.statSync(PKG_PATH).mtime.toISOString()
16 const pkg = JSON.parse(fs.readFileSync(PKG_PATH,'utf8'))
17 export const VERSION = pkg.version
18 export const RUNNING_BETA = VERSION.includes('-')
18 -
19 -export const API_VERSION = 8.4
20 -export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
21 -
22 -export const HFS_REPO = 'rejetto/hfs'
19 export const HFS_REPO_BRANCH = RUNNING_BETA ? 'next' : 'main'
24 -
25 -export const SPECIAL_URI = '/~/'
26 -export const FRONTEND_URI = SPECIAL_URI + 'frontend/'
27 -export const ADMIN_URI = SPECIAL_URI + 'admin/'
28 -export const API_URI = SPECIAL_URI + 'api/'
29 -export const PLUGINS_PUB_URI = SPECIAL_URI + 'plugins/'
30 -
31 -export const HTTP_OK = 200
32 -export const HTTP_NO_CONTENT = 204
33 -export const HTTP_PARTIAL_CONTENT = 206
34 -export const HTTP_MOVED_PERMANENTLY = 301
35 -export const HTTP_TEMPORARY_REDIRECT = 302
36 -export const HTTP_NOT_MODIFIED = 304
37 -export const HTTP_BAD_REQUEST = 400
38 -export const HTTP_UNAUTHORIZED = 401
39 -export const HTTP_FORBIDDEN = 403
40 -export const HTTP_NOT_FOUND = 404
41 -export const HTTP_METHOD_NOT_ALLOWED = 405
42 -export const HTTP_NOT_ACCEPTABLE = 406
43 -export const HTTP_CONFLICT = 409
44 -export const HTTP_PRECONDITION_FAILED = 412
45 -export const HTTP_PAYLOAD_TOO_LARGE = 413
46 -export const HTTP_RANGE_NOT_SATISFIABLE = 416
47 -export const HTTP_FOOL = 418
48 -export const HTTP_FAILED_DEPENDENCY = 424
49 -export const HTTP_SERVER_ERROR = 500
50 -export const HTTP_SERVICE_UNAVAILABLE = 503
51 -
20 export const IS_WINDOWS = process.platform === 'win32'
21 export const IS_MAC = process.platform === 'darwin'
22 export const IS_BINARY = !basename(process.execPath).includes('node') // this won't be node if pkg was used
src/cross-const.ts new
+33
@@ -0,0 +1,33 @@
1 +
2 +export const API_VERSION = 8.4
3 +export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
4 +
5 +export const HFS_REPO = 'rejetto/hfs'
6 +
7 +export const SPECIAL_URI = '/~/'
8 +export const FRONTEND_URI = SPECIAL_URI + 'frontend/'
9 +export const ADMIN_URI = SPECIAL_URI + 'admin/'
10 +export const API_URI = SPECIAL_URI + 'api/'
11 +export const PLUGINS_PUB_URI = SPECIAL_URI + 'plugins/'
12 +
13 +export const HTTP_OK = 200
14 +export const HTTP_NO_CONTENT = 204
15 +export const HTTP_PARTIAL_CONTENT = 206
16 +export const HTTP_MOVED_PERMANENTLY = 301
17 +export const HTTP_TEMPORARY_REDIRECT = 302
18 +export const HTTP_NOT_MODIFIED = 304
19 +export const HTTP_BAD_REQUEST = 400
20 +export const HTTP_UNAUTHORIZED = 401
21 +export const HTTP_FORBIDDEN = 403
22 +export const HTTP_NOT_FOUND = 404
23 +export const HTTP_METHOD_NOT_ALLOWED = 405
24 +export const HTTP_NOT_ACCEPTABLE = 406
25 +export const HTTP_CONFLICT = 409
26 +export const HTTP_PRECONDITION_FAILED = 412
27 +export const HTTP_PAYLOAD_TOO_LARGE = 413
28 +export const HTTP_RANGE_NOT_SATISFIABLE = 416
29 +export const HTTP_FOOL = 418
30 +export const HTTP_FAILED_DEPENDENCY = 424
31 +export const HTTP_SERVER_ERROR = 500
32 +export const HTTP_SERVICE_UNAVAILABLE = 503
33 +
src/cross.ts
+1
@@ -2,6 +2,7 @@
2 // all content here is shared between client and server
3 import _ from 'lodash'
4 import { VfsNode } from './vfs'
5 +export * from './cross-const'
6
7 export const REPO_URL = 'https://github.com/rejetto/hfs/'
8 export const WIKI_URL = REPO_URL + 'wiki/'