fix: admin/plugins: was not showing the partial list in case of errors
Massimo Melina committed
Sep 28, 2023 at 00:16 UTC
4dec055d8e732d109aada8143b7ed0822eef683e
7 files changed
+110
-116
admin/src/DataTable.ts
+4
-2
@@ -1,5 +1,5 @@
1
import { DataGrid, DataGridProps, GridColDef, GridValidRowModel, useGridApiRef } from '@mui/x-data-grid'
2
-import { Box, BoxProps, Breakpoint, LinearProgress, useTheme } from '@mui/material'
2
+import { Alert, Box, BoxProps, Breakpoint, LinearProgress, useTheme } from '@mui/material'
3
import { useWindowSize } from 'usehooks-ts'
4
import { createElement as h, Fragment, ReactNode, useEffect, useMemo, useRef, useState } from 'react'
5
import { newDialog, onlyTruthy } from '@hfs/shared'
@@ -17,8 +17,9 @@ interface DataTableProps<R extends GridValidRowModel=any> extends Omit<DataGridP
17
actionsProps?: Partial<GridColDef<R>> & { hideUnder?: Breakpoint | number }
18
initializing?: boolean
19
noRows?: ReactNode
20
+ error?: ReactNode
21
}
21
-export function DataTable({ columns, initialState={}, actions, actionsProps, initializing, noRows, ...rest }: DataTableProps) {
22
+export function DataTable({ columns, initialState={}, actions, actionsProps, initializing, noRows, error, ...rest }: DataTableProps) {
23
const { width } = useWindowSize()
24
const theme = useTheme()
25
const apiRef = useGridApiRef()
@@ -80,6 +81,7 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
81
return null
82
83
return h(Fragment, {},
84
+ error && h(Alert, { severity: 'error' }, error),
85
initializing && h(Box, { position: 'relative' },
86
h(LinearProgress, { // differently from "loading", this is not blocking user interaction
87
sx: { position: 'absolute', width: 'calc(100% - 2px)', borderRadius: 1, m: '1px 1px' }
admin/src/InstalledPlugins.ts
+2
-10
@@ -2,7 +2,7 @@
2
3
import { apiCall, useApiList } from './api'
4
import { createElement as h, Fragment, ReactNode } from 'react'
5
-import { Alert, Box, Link, Tooltip } from '@mui/material'
5
+import { Box, Link, Tooltip } from '@mui/material'
6
import { DataTable } from './DataTable'
7
import { Delete, Error as ErrorIcon, PlayCircle, Settings, StopCircle, Upgrade } from '@mui/icons-material'
8
import { IconBtn, prefix, with_, xlate } from './misc'
@@ -14,10 +14,9 @@ import FileField from './FileField'
14
15
export default function InstalledPlugins({ updates }: { updates?: true }) {
16
const { list, updateEntry, error, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
17
- if (error)
18
- return showError(error)
17
const size = 'small'
18
return h(DataTable, {
19
+ error,
20
rows: list.length ? list : [], // workaround for DataGrid bug causing 'no rows' message to be not displayed after 'loading' was also used
21
initializing,
22
disableColumnSelector: true,
@@ -158,13 +157,6 @@ const type2comp = {
157
real_path: FileField,
158
}
159
161
-export function showError(error: any) {
162
- return h(Alert, { severity: 'error' }, xlate(error, {
163
- github_quota: "Request denied. You may have reached the limit, retry later.",
164
- ENOTFOUND: "Couldn't reach github.com",
165
- }))
166
-}
167
-
160
export async function startPlugin(id: string) {
161
await apiCall('start_plugin', { id })
162
toast("Plugin started", h(PlayCircle, { color: 'success' }))
admin/src/LangPage.ts
+2
-1
@@ -14,7 +14,7 @@ export default function LangPage() {
14
const { list, error, connecting, reload } = useApiList('get_langs')
15
const langs = useMemo(() => ['en', ..._.uniq(list.map(x => x.code))], [list])
16
const large = useBreakpoint('md')
17
- return error || h(Fragment, {},
17
+ return h(Fragment, {},
18
large && h(Alert, { severity: 'info' }, "Translation is limited to Front-end, it doesn't apply to Admin-panel"),
19
h(Box, { mt: 1, maxWidth: '40em', flex: 1, display: 'flex', flexDirection: 'column' },
20
h(Box, { mb: 1, display: 'flex' },
@@ -23,6 +23,7 @@ export default function LangPage() {
23
h(ForceLang, { langs }),
24
),
25
h(DataTable, {
26
+ error,
27
loading: connecting,
28
rows: list as any,
29
hideFooter: true,
admin/src/LogsPage.ts
+1
-2
@@ -28,9 +28,8 @@ function LogFile({ file }: { file: string }) {
28
x.notes = notes
29
}
30
})
31
- if (error)
32
- return error
31
return h(DataTable, {
32
+ error,
33
loading: connecting,
34
rows: list as any,
35
componentsProps: {
admin/src/MonitorPage.ts
+98
-98
@@ -106,107 +106,107 @@ function Connections() {
106
}
107
}),
108
),
109
- error ? h(Alert, { severity: 'error' }, error)
110
- : h(DataTable, {
111
- rows,
112
- noRows: filtered && "No downloads at the moment",
113
- columns: [
114
- {
115
- field: 'ip',
116
- headerName: "Address",
117
- flex: 1,
118
- maxWidth: 400,
119
- renderCell: ({ row, value }) => ipForUrl(value) + ' :' + row.port,
120
- mergeRender: { other: 'user', fontSize: 'small' },
121
- },
122
- {
123
- field: 'user',
124
- headerName: "User",
125
- hideUnder: 'md',
126
- },
127
- {
128
- field: 'started',
129
- headerName: "Started",
130
- type: 'dateTime',
131
- width: 100,
132
- hideUnder: 'lg',
133
- valueFormatter: ({ value }) => new Date(value as string).toLocaleTimeString()
134
- },
135
- {
136
- field: 'path',
137
- headerName: "File",
138
- flex: 1.5,
139
- renderCell({ value, row }) {
140
- if (!value) return
141
- if (row.archive)
142
- return h(Fragment, {},
143
- h(FolderZip, { sx: { mr: 1 } }),
144
- row.archive,
145
- h(Box, { ml: 2, color: 'text.secondary' }, value)
146
- )
147
- const i = value?.lastIndexOf('/')
109
+ h(DataTable, {
110
+ error,
111
+ rows,
112
+ noRows: filtered && "No downloads at the moment",
113
+ columns: [
114
+ {
115
+ field: 'ip',
116
+ headerName: "Address",
117
+ flex: 1,
118
+ maxWidth: 400,
119
+ renderCell: ({ row, value }) => ipForUrl(value) + ' :' + row.port,
120
+ mergeRender: { other: 'user', fontSize: 'small' },
121
+ },
122
+ {
123
+ field: 'user',
124
+ headerName: "User",
125
+ hideUnder: 'md',
126
+ },
127
+ {
128
+ field: 'started',
129
+ headerName: "Started",
130
+ type: 'dateTime',
131
+ width: 100,
132
+ hideUnder: 'lg',
133
+ valueFormatter: ({ value }) => new Date(value as string).toLocaleTimeString()
134
+ },
135
+ {
136
+ field: 'path',
137
+ headerName: "File",
138
+ flex: 1.5,
139
+ renderCell({ value, row }) {
140
+ if (!value) return
141
+ if (row.archive)
142
return h(Fragment, {},
149
- row.op && h(IconProgress, {
150
- icon: row.op === 'upload' ? Upload : Download,
151
- progress: row.opProgress ?? row.opOffset,
152
- offset: row.opOffset,
153
- addTitle: row.opTotal && h('div', {}, "Total: " + formatBytes(row.opTotal)),
154
- sx: { mr: 1 }
155
- }),
156
- h(Box, {}, value.slice(i + 1),
157
- i > 0 && h(Box, { ml: 2, fontSize: 'x-small', color: 'text.secondary' }, value.slice(0, i))
158
- ),
143
+ h(FolderZip, { sx: { mr: 1 } }),
144
+ row.archive,
145
+ h(Box, { ml: 2, color: 'text.secondary' }, value)
146
)
160
- }
161
- },
162
- {
163
- field: 'outSpeed',
164
- headerName: "Speed",
165
- width: 110,
166
- hideUnder: 'sm',
167
- type: 'number',
168
- renderCell: ({ value, row }) => formatSpeed(Math.max(value||0, row.inSpeed||0) || undefined),
169
- mergeRender: { other: 'sent', fontSize: 'small', textAlign: 'right' }
170
- },
171
- {
172
- field: 'sent',
173
- headerName: "Sent",
174
- type: 'number',
175
- hideUnder: 'md',
176
- renderCell: ({ value, row}) => formatBytes(Math.max(value||0, row.got||0))
177
- },
178
- {
179
- field: 'v',
180
- headerName: "Protocol",
181
- align: 'center',
182
- hideUnder: Infinity,
183
- renderCell: ({ value, row }) => h(Fragment, {},
184
- "IPv" + value,
185
- iconTooltip(Lock, "HTTPS", { opacity: .5 })
147
+ const i = value?.lastIndexOf('/')
148
+ return h(Fragment, {},
149
+ row.op && h(IconProgress, {
150
+ icon: row.op === 'upload' ? Upload : Download,
151
+ progress: row.opProgress ?? row.opOffset,
152
+ offset: row.opOffset,
153
+ addTitle: row.opTotal && h('div', {}, "Total: " + formatBytes(row.opTotal)),
154
+ sx: { mr: 1 }
155
+ }),
156
+ h(Box, {}, value.slice(i + 1),
157
+ i > 0 && h(Box, { ml: 2, fontSize: 'x-small', color: 'text.secondary' }, value.slice(0, i))
158
+ ),
159
)
187
- },
188
- {
189
- field: 'agent',
190
- headerName: "Agent",
191
- hideUnder: 'lg',
192
- },
193
- ],
194
- actionsProps: { hideUnder: 'sm' },
195
- actions: ({ row }) => [
196
- h(IconBtn, {
197
- icon: LinkOff,
198
- title: "Disconnect",
199
- onClick: () => apiCall('disconnect', _.pick(row, ['ip', 'port']))
200
- .then(() => toast("Disconnection requested")),
201
- }),
202
- h(IconBtn, {
203
- icon: Block,
204
- title: "Block IP",
205
- disabled: row.ip === props?.you,
206
- onClick: () => blockIp(row.ip),
207
- }),
208
- ]
209
- })
160
+ }
161
+ },
162
+ {
163
+ field: 'outSpeed',
164
+ headerName: "Speed",
165
+ width: 110,
166
+ hideUnder: 'sm',
167
+ type: 'number',
168
+ renderCell: ({ value, row }) => formatSpeed(Math.max(value||0, row.inSpeed||0) || undefined),
169
+ mergeRender: { other: 'sent', fontSize: 'small', textAlign: 'right' }
170
+ },
171
+ {
172
+ field: 'sent',
173
+ headerName: "Sent",
174
+ type: 'number',
175
+ hideUnder: 'md',
176
+ renderCell: ({ value, row}) => formatBytes(Math.max(value||0, row.got||0))
177
+ },
178
+ {
179
+ field: 'v',
180
+ headerName: "Protocol",
181
+ align: 'center',
182
+ hideUnder: Infinity,
183
+ renderCell: ({ value, row }) => h(Fragment, {},
184
+ "IPv" + value,
185
+ iconTooltip(Lock, "HTTPS", { opacity: .5 })
186
+ )
187
+ },
188
+ {
189
+ field: 'agent',
190
+ headerName: "Agent",
191
+ hideUnder: 'lg',
192
+ },
193
+ ],
194
+ actionsProps: { hideUnder: 'sm' },
195
+ actions: ({ row }) => [
196
+ h(IconBtn, {
197
+ icon: LinkOff,
198
+ title: "Disconnect",
199
+ onClick: () => apiCall('disconnect', _.pick(row, ['ip', 'port']))
200
+ .then(() => toast("Disconnection requested")),
201
+ }),
202
+ h(IconBtn, {
203
+ icon: Block,
204
+ title: "Block IP",
205
+ disabled: row.ip === props?.you,
206
+ onClick: () => blockIp(row.ip),
207
+ }),
208
+ ]
209
+ })
210
)
211
}
212
admin/src/OnlinePlugins.ts
+2
-3
@@ -7,7 +7,7 @@ import { 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, showError, startPlugin } from './InstalledPlugins'
10
+import { renderName, startPlugin } from './InstalledPlugins'
11
import { state, useSnapState } from './state'
12
import { alertDialog, confirmDialog } from './dialog'
13
@@ -16,8 +16,6 @@ export default function OnlinePlugins() {
16
const debouncedSearch = useDebounce(search, 1000)
17
const { list, error, initializing } = useApiList('get_online_plugins', { text: debouncedSearch })
18
const snap = useSnapState()
19
- if (error)
20
- return showError(error)
19
return h(Fragment, {},
20
h(StringField, {
21
value: search,
@@ -27,6 +25,7 @@ export default function OnlinePlugins() {
25
label: "Search text"
26
}),
27
h(DataTable, {
28
+ error,
29
rows: list.length ? list : [], // workaround for DataGrid bug causing 'no rows' message to be not displayed after 'loading' was also used
30
noRows: "No compatible plugins have been found",
31
initializing,
admin/src/misc.ts
+1
@@ -14,6 +14,7 @@ export async function manipulateConfig(k: string, work:(data:any) => any) {
14
15
export function err2msg(code: string) {
16
return {
17
+ github_quota: "Request denied. You may have reached the limit, retry later.",
18
ENOENT: "Not found",
19
ENOTDIR: "Not a folder",
20
}[code] || code