ux: admin/plugins: resume-plugins button will resort the list so that you can easily see what did actually start

Massimo Melina committed Mar 5, 2026 at 19:22 UTC 196cf3b5b4b589223a2b03df056142c0684dad15
1 file changed +11 -5
admin/src/InstalledPlugins.ts
+11 -5
@@ -1,7 +1,7 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { apiCall, useApiEx, useApiList } from './api'
4 -import { createElement as h, Fragment, useEffect } from 'react'
4 +import { createElement as h, Fragment, useEffect, useState } from 'react'
5 import { Box, Breakpoint, Link, Paper, Table, TableCell, TableRow, useTheme } from '@mui/material'
6 import { DataTable, DataTableColumn } from './DataTable'
7 import {
@@ -9,7 +9,7 @@ import {
9 } from '@mui/icons-material'
10 import {
11 CFG, Html, HTTP_FAILED_DEPENDENCY, md, newObj, prefix, with_, xlate, formatTime, formatDate, replaceStringToReact,
12 - callable, tryJson, useAutoScroll, NBSP, isPrimitive, HIDE_IN_TESTS
12 + callable, tryJson, useAutoScroll, NBSP, isPrimitive, HIDE_IN_TESTS, wait
13 } from './misc'
14 import { alertDialog, confirmDialog, formDialog, toast } from './dialog'
15 import _ from 'lodash'
@@ -31,13 +31,19 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
31 const { list, error, setList, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins', {}, {
32 map(x: any) { x.config &&= tryJson(x.config, s => evalWrapper('()=>('+s+')')()) }
33 })
34 + const [sortAgain, setSortAgain] = useState(0)
35 useEffect(() => {
36 setList(list =>
37 _.sortBy(list, x => (x.error ? 0 : x.started ? 1 : 2) + treatPluginName(x.repo?.split('/').reverse().join('/') || x.id).toLowerCase()))
37 - }, [list.length]);
38 + }, [list.length, sortAgain]);
39 const size = 'small'
40 const { pause, pauseButton } = usePauseButton("plugins", () => getSingleConfig(CFG.suspend_plugins).then(x => !x), {
40 - onClick: () => apiCall('set_config', { values: { [CFG.suspend_plugins]: !pause } })
41 + async onClick() {
42 + await apiCall('set_config', { values: { [CFG.suspend_plugins]: !pause } })
43 + if (!pause) return
44 + await wait(3000)
45 + setSortAgain(Date.now())
46 + }
47 })
48 const theme = useTheme()
49 return h(DataTable, {
@@ -124,7 +130,7 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
130 } : {
131 icon: PlayCircle,
132 title: `Start ${id}`,
127 - disabled: pause,
133 + disabled: pause && "All plugins are paused – Click the Resume button below",
134 size,
135 onClick: () => startPlugin(id),
136 }),