admin/plugins: uninstall button
Massimo Melina committed
May 11, 2022 at 20:21 UTC
ed4bfdd837178988abcd431beacee9e00de8ce43
3 files changed
+38
-13
admin/src/InstalledPlugins.ts
+11
-3
@@ -2,7 +2,7 @@ import { apiCall, useApiList } from './api'
2
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 { Error, PlayCircle, Settings, StopCircle } from '@mui/icons-material'
5
+import { Delete, Error, PlayCircle, Settings, StopCircle } from '@mui/icons-material'
6
import { IconBtn } from './misc'
7
import { formDialog, toast } from './dialog'
8
import _ from 'lodash'
@@ -46,7 +46,7 @@ export default function InstalledPlugins() {
46
},
47
{
48
field: "actions",
49
- width: 80,
49
+ width: 120,
50
align: 'center',
51
hideSortIcons: true,
52
disableColumnMenu: true,
@@ -69,7 +69,7 @@ export default function InstalledPlugins() {
69
h(IconBtn, {
70
icon: Settings,
71
title: "Configuration",
72
- disabled: !config,
72
+ disabled: !config && "No configuration available for this plugin",
73
async onClick() {
74
const pl = await apiCall('get_plugin', { id })
75
const values = await formDialog({
@@ -83,6 +83,14 @@ export default function InstalledPlugins() {
83
toast("Configuration saved")
84
}
85
}),
86
+ h(IconBtn, {
87
+ icon: Delete,
88
+ title: "Uninstall",
89
+ async onClick() {
90
+ await apiCall('uninstall_plugin', { id })
91
+ toast("Plugin uninstalled")
92
+ }
93
+ }),
94
)
95
}
96
},
server/src/api.plugins.ts
+15
-10
@@ -6,16 +6,16 @@ import {
6
mapPlugins,
7
parsePluginSource,
8
Plugin, pluginsConfig,
9
- PATH as PLUGINS_PATH
9
+ PATH as PLUGINS_PATH, isPluginRunning, enablePlugin
10
} from './plugins'
11
import _ from 'lodash'
12
import assert from 'assert'
13
-import { httpsString, httpsStream, objSameKeys, onOff, same } from './misc'
13
+import { httpsString, httpsStream, objSameKeys, onOff, same, wait } from './misc'
14
import { ApiError, ApiHandlers, sendList } from './apiMiddleware'
15
import events from './events'
16
import unzipper from 'unzipper'
17
-import { createWriteStream } from 'fs'
18
-import { access, mkdir } from 'fs/promises'
17
+import { createWriteStream, mkdirSync } from 'fs'
18
+import { rm } from 'fs/promises'
19
20
const DIST_ROOT = 'dist/'
21
@@ -39,11 +39,7 @@ const apis: ApiHandlers = {
39
async set_plugin({ id, enabled, config }) {
40
assert(id, 'id')
41
if (enabled !== undefined)
42
- enablePlugins.set( arr =>
43
- arr.includes(id) === enabled ? arr
44
- : enabled ? [...arr, id]
45
- : arr.filter((x: string) => x !== id)
46
- )
42
+ enablePlugin(id, enabled)
43
if (config) {
44
const fields = getPluginConfigFields(id)
45
config = _.pickBy(config, (v, k) =>
@@ -108,10 +104,19 @@ const apis: ApiHandlers = {
104
async download_plugin({ id }) {
105
if (downloading[id])
106
return new ApiError(409, "already downloading")
111
- downloadPlugin(id).then()
107
+ await downloadPlugin(id)
108
return {}
109
},
110
111
+ async uninstall_plugin({ id }) {
112
+ while (isPluginRunning(id)) {
113
+ enablePlugin(id, false)
114
+ await wait(500)
115
+ }
116
+ await rm(PLUGINS_PATH + '/' + id, { recursive: true, force: true })
117
+ return {}
118
+ }
119
+
120
}
121
122
type DownloadStatus = true | undefined
server/src/plugins.ts
+12
@@ -22,6 +22,18 @@ export const DISABLING_POSTFIX = '-disabled'
22
23
const plugins: Record<string, Plugin> = {}
24
25
+export function isPluginRunning(id: string) {
26
+ return plugins[id]?.started
27
+}
28
+
29
+export function enablePlugin(id: string, state=true) {
30
+ enablePlugins.set( arr =>
31
+ arr.includes(id) === state ? arr
32
+ : state ? [...arr, id]
33
+ : arr.filter((x: string) => x !== id)
34
+ )
35
+}
36
+
37
export function mapPlugins<T>(cb:(plugin:Readonly<Plugin>, pluginName:string)=> T) {
38
return _.map(plugins, (pl,plName) => {
39
try { return cb(pl,plName) }