@samitouri / QOSami-HFS / commits / 63714b5b

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