admin/logs: show logs file size
Massimo Melina committed
Apr 11, 2025 at 12:25 UTC
541b40080d9570695dec4cec8180e8b23a81dfc7
3 files changed
+30
-4
admin/src/LogsPage.ts
+15
-3
@@ -3,10 +3,11 @@
3
import { createElement as h, Fragment, ReactNode, useEffect, useMemo, useState } from 'react'
4
import httpCodes from './httpCodes'
5
import { Box, Tab, Tabs } from '@mui/material'
6
-import { API_URL, apiCall, useApi, useApiList } from './api'
6
+import { PageProps } from './App'
7
+import { API_URL, apiCall, useApi, useApiEx, useApiList } from './api'
8
import { DataTable, DataTableColumn, DataTableProps } from './DataTable'
9
import {
9
- CFG, Dict, formatBytes, HTTP_UNAUTHORIZED, newDialog, prefix, shortenAgent, splitAt, tryJson, md, typedKeys,
10
+ CFG, Dict, formatBytes, HTTP_UNAUTHORIZED, newDialog, prefix, shortenAgent, splitAt, tryJson, md, typedKeys, with_,
11
_dbg, mapFilter, safeDecodeURIComponent, stringAfter, onlyTruthy, formatTimestamp, formatSpeed, copyTextToClipboard
12
} from '@hfs/shared'
13
import {
@@ -33,12 +34,23 @@ const logLabels = {
34
35
let reloadIps: any
36
36
-export default function LogsPage() {
37
+export default function LogsPage({ setTitleSide }: PageProps) {
38
const [tab, setTab] = useState(0)
39
const files = typedKeys(logLabels)
40
const shorterLabels = !useBreakpoint('sm') && { error_log: "Not", console: h(Terminal), disconnections: h(LinkOff) }
41
const file = files[tab]
42
const fileAvailable = file.endsWith('log')
43
+
44
+ const logInfo = useApiEx('get_log_info')
45
+ setTitleSide(useMemo(() => fileAvailable && (logInfo.element || with_(logInfo.data, data =>
46
+ h(Box, { fontSize: 'smaller' },
47
+ `Current: ${formatBytes(_.sum(Object.values(data.current)))}`,
48
+ h('br'),
49
+ with_(Object.values(data.rotated).flat(), rotatedAsArray =>
50
+ `Archived: ${formatBytes(_.sumBy(rotatedAsArray, 'size'))} / ${rotatedAsArray.length} files`)
51
+ )
52
+ )), [logInfo.element, logInfo.data, fileAvailable]))
53
+
54
return h(Fragment, {},
55
h(Flex, { gap: 0 },
56
h(Tabs, { value: tab, onChange(ev,i){ setTab(i) } },
src/api.log.ts
+7
-1
@@ -4,13 +4,19 @@ import { consoleLog } from './consoleLog'
4
import { HTTP_BAD_REQUEST, HTTP_NOT_ACCEPTABLE, HTTP_NOT_FOUND, wait } from './cross'
5
import { apiAssertTypes } from './misc'
6
import events from './events'
7
-import { loggers } from './log'
7
+import { getRotatedFiles, loggers } from './log'
8
+import { stat } from 'fs/promises'
9
import { SendListReadable } from './SendList'
10
import { forceDownload, serveFile } from './serveFile'
11
import { ips } from './ips'
12
import { disconnectionsLog } from './connections'
13
14
export default {
15
+ async get_log_info() {
16
+ const current = Object.fromEntries(await Promise.all(loggers.map(async x => [x.name, await stat(x.path).then(s => s.size)])))
17
+ return { current, rotated: await getRotatedFiles() }
18
+ },
19
+
20
async get_log_file({ file = 'log', range = '' }, ctx) { // this is limited to logs on file, and serves the file instead of a list of records
21
const log = _.find(loggers, { name: file })
22
if (!log)
src/log.ts
+8
@@ -15,6 +15,7 @@ import events from './events'
15
import { getConnection } from './connections'
16
import { app } from './index'
17
import { logGui } from './serveGuiFiles'
18
+import glob from 'fast-glob'
19
20
class Logger {
21
stream?: Writable
@@ -172,6 +173,13 @@ function doubleDigit(n: number) {
173
return n > 9 ? n : '0'+n
174
}
175
176
+export async function getRotatedFiles() {
177
+ return Object.fromEntries(await Promise.all(loggers.map(async x => {
178
+ const mask = strinsert(x.path, x.path.length - extname(x.path).length, '-2*') // including 2, initial digit of the year, will only take rotated files and not "-error"
179
+ return [x.name, (await glob(mask, { stats: true })).map(x => ({ path: x.path, size: x.stats?.size }))]
180
+ })))
181
+}
182
+
183
// dump console.error to file
184
let debugLogFile = createWriteStream('debug.log', { flags: 'a' })
185
debugLogFile.once('open', () => {