@samitouri / QOSami-HFS / commits / 42c98583

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 }