admin/logs: moved download button outside log options, as it's not an option
Massimo Melina committed
Feb 9, 2024 at 09:36 UTC
42c98583338cfe52ed1a6ef6ae3b8f836a638985
3 files changed
+50
-22
admin/src/DataTable.ts
+12
-3
@@ -1,5 +1,5 @@
1
-import { DataGrid, DataGridProps, enUS, getGridStringOperators, GridColDef, GridValidRowModel, useGridApiRef
2
-} from '@mui/x-data-grid'
1
+import { DataGrid, DataGridProps, enUS, getGridStringOperators, GridColDef, GridFooter, GridFooterContainer,
2
+ GridValidRowModel, useGridApiRef } from '@mui/x-data-grid'
3
import { Alert, Box, BoxProps, Breakpoint, LinearProgress, useTheme } from '@mui/material'
4
import { useWindowSize } from 'usehooks-ts'
5
import { createElement as h, Fragment, ReactNode, useEffect, useMemo, useRef, useState } from 'react'
@@ -21,8 +21,9 @@ interface DataTableProps<R extends GridValidRowModel=any> extends Omit<DataGridP
21
noRows?: ReactNode
22
error?: ReactNode
23
compact?: true
24
+ addToFooter?: ReactNode
25
}
25
-export function DataTable({ columns, initialState={}, actions, actionsProps, initializing, noRows, error, compact, ...rest }: DataTableProps) {
26
+export function DataTable({ columns, initialState={}, actions, actionsProps, initializing, noRows, error, compact, addToFooter, ...rest }: DataTableProps) {
27
const { width } = useWindowSize()
28
const theme = useTheme()
29
const apiRef = useGridApiRef()
@@ -116,6 +117,10 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
117
apiRef,
118
slots: {
119
...(noRows || initializing) && { noRowsOverlay: () => initializing ? null : h(Center, {}, noRows) },
120
+ footer: CustomFooter,
121
+ },
122
+ slotProps: {
123
+ footer: { add: addToFooter } as any
124
},
125
onCellClick({ field, row }) {
126
if (field === ACTIONS) return
@@ -170,3 +175,7 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
175
: value
176
}
177
}
178
+
179
+function CustomFooter({ add, ...props }: { add: ReactNode }) {
180
+ return h(GridFooterContainer, props, h(Box, { ml: 1 }, add), h(GridFooter, { sx: { border: 'none' } }))
181
+}
admin/src/LogsPage.ts
+16
-16
@@ -1,6 +1,6 @@
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 { createElement as h, Fragment, useMemo, useState } from 'react';
3
+import { createElement as h, Fragment, ReactNode, useMemo, useState } from 'react';
4
import { Box, Tab, Tabs } from '@mui/material'
5
import { API_URL, useApiList } from './api'
6
import { DataTable } from './DataTable'
@@ -20,13 +20,6 @@ import { useDialogBarColors } from './dialog'
20
export default function LogsPage() {
21
const [tab, setTab] = useState(0)
22
const files = typedKeys(logLabels)
23
- const { pause, pauseButton } = usePauseButton()
24
- const [showApi, showApiButton] = useToggleButton(v => ({
25
- title: "Show/hide APIs",
26
- icon: SmartToy,
27
- sx: { rotate: v ? '0deg' : '180deg' },
28
- disabled: tab >= 2,
29
- }), true)
23
const shorterLabels = !useBreakpoint('sm') && { error_log: "Errors" }
24
const file = files[tab]
25
return h(Fragment, {},
@@ -38,11 +31,10 @@ export default function LogsPage() {
31
sx: { minWidth: 0, px: { xs: 1.5, sm: 2 } } // save space
32
}))),
33
h(Box, { flex: 1 }),
41
- showApiButton,
42
- pauseButton,
34
+ h(IconBtn, { icon: Download, title: "Download as file", link: API_URL + `get_log_file?file=${file}` }),
35
h(IconBtn, { icon: Settings, title: "Options", onClick: showLogOptions })
36
),
45
- h(LogFile, { key: tab, pause, showApi, file }), // without key, some state is accidentally preserved across files
37
+ h(LogFile, { file, key: tab }), // without key, some state is accidentally preserved across files
38
)
39
40
function showLogOptions() {
@@ -61,10 +53,6 @@ export default function LogsPage() {
53
}>, {
54
keys: [ CFG.log, CFG.error_log, CFG.log_rotation, CFG.dont_log_net, CFG.log_gui, CFG.log_api, CFG.log_ua ],
55
barSx: { gap: 2, width: '100%', ...useDialogBarColors() },
64
- addToBar: [
65
- h(Box, { flex: 1}),
66
- h(Btn, { icon: Download, variant: 'outlined', link: API_URL + `get_log_file?file=${file}` }, "Download file"),
67
- ],
56
form: {
57
stickyBar: true,
58
fields: [
@@ -89,9 +77,16 @@ export default function LogsPage() {
77
}
78
}
79
92
-function LogFile({ file, pause, showApi }: { file: string, pause?: boolean, showApi: boolean }) {
80
+function LogFile({ file, addToFooter }: { file: string, addToFooter?: ReactNode }) {
81
const [showCountry, setShowCountry] = useState(false)
82
const [showAgent, setShowAgent] = useState(false)
83
+ const { pause, pauseButton } = usePauseButton()
84
+ const [showApi, showApiButton] = useToggleButton(v => ({
85
+ title: "Show/hide APIs",
86
+ icon: SmartToy,
87
+ sx: { rotate: v ? '0deg' : '180deg' },
88
+ disabled: file === 'console',
89
+ }), true)
90
const { list, error, connecting } = useApiList('get_log', { file }, {
91
invert: true,
92
pause,
@@ -127,6 +122,11 @@ function LogFile({ file, pause, showApi }: { file: string, pause?: boolean, show
122
showLastButton: true,
123
}
124
},
125
+ addToFooter: h(Box, {}, // 4 icons don't fit the tabs row on mobile
126
+ pauseButton,
127
+ showApiButton,
128
+ addToFooter,
129
+ ),
130
columns: file === 'console' ? [
131
tsColumn,
132
{
admin/src/mui.ts
+22
-3
@@ -3,7 +3,17 @@
3
4
import { PauseCircle, PlayCircle, Refresh, SvgIconComponent } from '@mui/icons-material'
5
import { SxProps } from '@mui/system'
6
-import { createElement as h, FC, forwardRef, Fragment, ReactElement, ReactNode, useCallback, useState } from 'react'
6
+import {
7
+ createElement as h,
8
+ FC,
9
+ forwardRef,
10
+ Fragment,
11
+ ReactElement,
12
+ ReactNode,
13
+ useCallback,
14
+ useEffect,
15
+ useState
16
+} from 'react'
17
import { Box, BoxProps, Breakpoint, ButtonProps, CircularProgress, IconButton, IconButtonProps, Link, LinkProps,
18
Tooltip, TooltipProps, useMediaQuery } from '@mui/material'
19
import { formatPerc, isIpLan, isIpLocalHost, prefix, WIKI_URL } from '../../src/cross'
@@ -32,6 +42,14 @@ export function useLogBreakpoint() {
42
console.log('BREAKPOINT', breakpoints[_.findIndex(breakpoints.map(x => useBreakpoint(x)), x => x)])
43
}
44
45
+// for debug purposes
46
+export function useLogMount(name: string) {
47
+ useEffect(() => {
48
+ console.log('MOUNT', name)
49
+ return () => console.log('UNMOUNT', name)
50
+ }, [])
51
+}
52
+
53
interface IconProgressProps {
54
icon: SvgIconComponent,
55
progress: number,
@@ -107,7 +125,7 @@ interface IconBtnProps extends Omit<IconButtonProps, 'disabled'|'title'|'onClick
125
confirm?: string
126
doneMessage?: boolean | string // displayed only if the result of onClick !== false
127
tooltipProps?: Partial<TooltipProps>
110
- onClick: (...args: Parameters<NonNullable<IconButtonProps['onClick']>>) => Promisable<any>
128
+ onClick?: (...args: Parameters<NonNullable<IconButtonProps['onClick']>>) => Promisable<any>
129
}
130
131
export const IconBtn = forwardRef(({ title, icon, onClick, disabled, progress, link, tooltipProps, confirm, doneMessage, sx, ...rest }: IconBtnProps, ref: any) => {
@@ -233,11 +251,12 @@ export function LinkBtn({ ...rest }: LinkProps) {
251
})
252
}
253
236
-export function usePauseButton() {
254
+export function usePauseButton(props?: Partial<IconBtnProps>) {
255
const [go, btn] = useToggleButton(v => ({
256
title: "Pause",
257
icon: v ? PauseCircle : PlayCircle,
258
sx: { rotate: v ? '180deg' : '0deg' },
259
+ ...props,
260
}), true)
261
return { pause: !go, pauseButton: btn }
262
}