admin/logs: new "disconnections" log
Massimo Melina committed
Feb 21, 2025 at 12:18 UTC
63714b5b7dcd8157ba023f2157fcbc2dfc24a2ee
4 files changed
+25
-10
admin/src/LogsPage.ts
+4
-3
@@ -15,7 +15,7 @@ import {
15
} from './mui';
16
import { GridColDef } from '@mui/x-data-grid'
17
import _ from 'lodash'
18
-import { AutoDelete, ClearAll, Delete, Download, Settings, SmartToy } from '@mui/icons-material'
18
+import { AutoDelete, LinkOff, ClearAll, Delete, Download, Settings, SmartToy, Terminal } from '@mui/icons-material'
19
import { ConfigForm } from './ConfigForm'
20
import { BoolField, SelectField } from '@hfs/mui-grid-form'
21
import { toast, useDialogBarColors } from './dialog'
@@ -26,6 +26,7 @@ const logLabels = {
26
log: "Served",
27
error_log: "Not served",
28
console: "Console",
29
+ disconnections: "Disconnections",
30
ips: "IP's",
31
}
32
@@ -34,7 +35,7 @@ let reloadIps: any
35
export default function LogsPage() {
36
const [tab, setTab] = useState(0)
37
const files = typedKeys(logLabels)
37
- const shorterLabels = !useBreakpoint('sm') && { error_log: "Not" }
38
+ const shorterLabels = !useBreakpoint('sm') && { error_log: "Not", console: h(Terminal), disconnections: h(LinkOff) }
39
const file = files[tab]
40
const fileAvailable = file.endsWith('log')
41
return h(Fragment, {},
@@ -208,7 +209,7 @@ export function LogFile({ file, footerSide, hidden, limit, filter, ...rest }: Lo
209
flex: 1,
210
mergeRender: { k: { override: { valueFormatter: ({ value }) => value !== 'log' && value } } }
211
}
211
- ] : file === 'ips' ? [
212
+ ] : file === 'ips' || file === 'disconnections' ? [
213
tsColumn,
214
{
215
field: 'ip',
src/SendList.ts
+2
-2
@@ -61,7 +61,7 @@ export class SendListReadable<T> extends Readable {
61
else
62
this.processBuffer()
63
}
64
- add(rec: T | T[]) {
64
+ add(rec: T) {
65
this._push([LIST.add, rec])
66
}
67
remove(search: Partial<T>) {
@@ -72,7 +72,7 @@ export class SendListReadable<T> extends Readable {
72
if (op === LIST.remove) return
73
if (found) {
74
this.buffer.splice(idx, 1)
75
- if (op === LIST.add) return
75
+ if (op === LIST.add) return // assuming this never reached the client
76
}
77
this._push([LIST.remove, search])
78
}
src/api.log.ts
+8
-4
@@ -8,6 +8,7 @@ import { loggers } from './log'
8
import { SendListReadable } from './SendList'
9
import { forceDownload, serveFile } from './serveFile'
10
import { ips } from './ips'
11
+import { disconnectionsLog } from './connections'
12
13
export default {
14
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
@@ -30,11 +31,15 @@ export default {
31
return new SendListReadable({
32
bufferTime: 10,
33
async doAtStart(list) {
34
+ if (file === 'disconnections') {
35
+ for (const x of disconnectionsLog) list.add(x)
36
+ ctx.res.once('close', events.on('disconnection', x => list.add(x)))
37
+ return list.ready()
38
+ }
39
if (file === 'ips') {
40
for await (const [k, v] of ips.iterator())
41
list.add({ ip: k, ...v })
36
- list.ready()
37
- return
42
+ return list.ready()
43
}
44
if (file === 'console') {
45
for (const chunk of _.chunk(consoleLog, 1000)) { // avoid occupying the thread too long
@@ -42,9 +47,8 @@ export default {
47
list.add(x)
48
await wait(0)
49
}
45
- list.ready()
50
ctx.res.once('close', events.on('console', x => list.add(x)))
47
- return
51
+ return list.ready()
52
}
53
// for other logs we only provide updates. Use get_log_file to download past content
54
if (_.some(files, x => !_.find(loggers, { name: x })) )
src/connections.ts
+11
-1
@@ -3,6 +3,7 @@
3
import { Socket } from 'net'
4
import events from './events'
5
import { Context } from 'koa'
6
+import { ip2country } from './geo'
7
8
export class Connection {
9
readonly started = new Date()
@@ -77,10 +78,19 @@ export function updateConnection(conn: Connection, change: Partial<Connection>,
78
events.emit('connectionUpdated', conn, change)
79
}
80
81
+export const disconnectionsLog: { ts: Date, ip: string, country?: string, msg?: string }[] = []
82
+
83
export function disconnect(what: Context | Socket, debugLog='') {
84
if ('socket' in what)
85
what = what.socket
86
+ const ip = normalizeIp(what.remoteAddress || '')
87
if (debugLog)
84
- console.debug("disconnection:", debugLog, normalizeIp(what.remoteAddress || ''))
88
+ console.debug("disconnection:", debugLog, ip)
89
+ ip2country(ip).then(res => {
90
+ const rec = { ip, country: res || undefined, ts: new Date, msg: debugLog || undefined }
91
+ disconnectionsLog.unshift(rec)
92
+ disconnectionsLog.length = Math.min(1000, disconnectionsLog.length)
93
+ events.emit('disconnection', rec)
94
+ })
95
return what.destroy()
96
}
\ No newline at end of file