admin/logs: download button
Massimo Melina committed
Feb 8, 2024 at 23:28 UTC
af01f83ae770d3207a191eb7b1c40a8bbbf37609
3 files changed
+26
-6
admin/src/LogsPage.ts
+7
-2
@@ -11,7 +11,7 @@ import { NetmaskField, Flex, IconBtn, useBreakpoint, usePauseButton, useToggleBu
11
hTooltip } from './mui';
12
import { GridColDef } from '@mui/x-data-grid'
13
import _ from 'lodash'
14
-import { Settings, SmartToy } from '@mui/icons-material'
14
+import { Download, Settings, SmartToy } from '@mui/icons-material'
15
import md from './md'
16
import { ConfigForm } from './ConfigForm'
17
import { BoolField, SelectField } from '@hfs/mui-grid-form'
@@ -28,6 +28,7 @@ export default function LogsPage() {
28
disabled: tab >= 2,
29
}), true)
30
const shorterLabels = !useBreakpoint('sm') && { error_log: "Errors" }
31
+ const file = files[tab]
32
return h(Fragment, {},
33
h(Flex, { gap: 0 },
34
h(Tabs, { value: tab, onChange(ev,i){ setTab(i) } },
@@ -41,7 +42,7 @@ export default function LogsPage() {
42
pauseButton,
43
h(IconBtn, { icon: Settings, title: "Options", onClick: showLogOptions })
44
),
44
- h(LogFile, { key: tab, pause, showApi, file: files[tab] }), // without key, some state is accidentally preserved across files
45
+ h(LogFile, { key: tab, pause, showApi, file }), // without key, some state is accidentally preserved across files
46
)
47
48
function showLogOptions() {
@@ -60,6 +61,10 @@ export default function LogsPage() {
61
}>, {
62
keys: [ CFG.log, CFG.error_log, CFG.log_rotation, CFG.dont_log_net, CFG.log_gui, CFG.log_api, CFG.log_ua ],
63
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
+ ],
68
form: {
69
stickyBar: true,
70
fields: [
src/api.log.ts
+13
-1
@@ -1,15 +1,27 @@
1
import { ApiHandlers } from './apiMiddleware'
2
import _ from 'lodash'
3
import { consoleLog } from './consoleLog'
4
-import { HTTP_NOT_FOUND, tryJson, wait } from './cross'
4
+import { HTTP_NOT_ACCEPTABLE, HTTP_NOT_FOUND, tryJson, wait } from './cross'
5
import events from './events'
6
import { loggers } from './log'
7
import { createReadStream } from 'fs'
8
import readline from 'readline'
9
import { onOff } from './misc'
10
import { SendListReadable } from './SendList'
11
+import { serveFile } from './serveFile'
12
13
export default {
14
+ async get_log_file({ file = 'log' }, ctx) {
15
+ const log = _.find(loggers, { name: file })
16
+ if (!log)
17
+ throw HTTP_NOT_FOUND
18
+ if (!log.path)
19
+ throw HTTP_NOT_ACCEPTABLE
20
+ ctx.attachment(log.path)
21
+ await serveFile(ctx, log.path)
22
+ return null
23
+ },
24
+
25
get_log({ file = 'log' }, ctx) {
26
return new SendListReadable({
27
bufferTime: 10,
src/apiMiddleware.ts
+6
-3
@@ -3,7 +3,7 @@
3
import Koa from 'koa'
4
import createSSE from './sse'
5
import { Readable } from 'stream'
6
-import { asyncGeneratorToReadable, CFG, removeStarting } from './misc'
6
+import { asyncGeneratorToReadable, CFG, Promisable, removeStarting } from './misc'
7
import { HTTP_BAD_REQUEST, HTTP_FOOL, HTTP_NOT_FOUND } from './const'
8
import { defineConfig } from './config'
9
@@ -12,8 +12,8 @@ export class ApiError extends Error {
12
super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message))
13
}
14
}
15
-type ApiHandlerResult = Record<string,any> | ApiError | Readable | AsyncGenerator<any>
16
-export type ApiHandler = (params:any, ctx:Koa.Context) => ApiHandlerResult | Promise<ApiHandlerResult>
15
+type ApiHandlerResult = Record<string,any> | ApiError | Readable | AsyncGenerator<any> | null
16
+export type ApiHandler = (params:any, ctx:Koa.Context) => Promisable<ApiHandlerResult>
17
export type ApiHandlers = Record<string, ApiHandler>
18
19
const logApi = defineConfig(CFG.log_api, true)
@@ -44,12 +44,15 @@ export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
44
else if (typeof (v as any)?.[0] === 'string')
45
(v as string[]).forEach((x,i) => fixUri(v,i))
46
res = await apiFun(params, ctx)
47
+ if (res === null) return
48
49
function fixUri(obj: any, k: string | number) {
50
obj[k] = removeStarting(ctx.state.revProxyPath, obj[k])
51
}
52
}
53
catch(e) {
54
+ if (typeof e === 'number')
55
+ e = new ApiError(HTTP_NOT_FOUND)
56
res = e
57
}
58
if (isAsyncGenerator(res))