main
ts 97 lines 3.65 KB
Raw
1 import { ApiHandlers } from './apiMiddleware'
2 import _ from 'lodash'
3 import { consoleLog } from './consoleLog'
4 import { HTTP_BAD_REQUEST, HTTP_NOT_ACCEPTABLE, HTTP_NOT_FOUND, wait } from './cross'
5 import { apiAssertTypes, statWithTimeout } from './misc'
6 import events from './events'
7 import { getRotatedFiles, 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_info() {
15 const current = Object.fromEntries(await Promise.all(loggers.map(async x => [x.name, await statWithTimeout(x.path).then(s => s.size, () => 0)])))
16 return { current, rotated: await getRotatedFiles() }
17 },
18
19 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
20 apiAssertTypes({ string: { file, range } })
21 const log = _.find(loggers, { name: file })
22 if (!log)
23 throw HTTP_NOT_FOUND
24 if (!log.path)
25 throw HTTP_NOT_ACCEPTABLE
26 forceDownload(ctx, log.path)
27 if (range)
28 ctx.request.header.range = `bytes=${range}`
29 if (ctx.method === 'POST') // this would cause method_not_allowed
30 ctx.method = 'GET'
31 await serveFile(ctx, log.path)
32 return null
33 },
34
35 get_log({ file = 'log' }, ctx) {
36 apiAssertTypes({ string: { file } })
37 const files = file.split('|') // potentially more than one
38 return new SendListReadable({
39 bufferTime: 10,
40 async doAtStart(list) {
41 if (file === 'disconnections') {
42 for (const x of disconnectionsLog) list.add(x)
43 ctx.res.once('close', events.on('disconnection', x => list.add(x)))
44 return list.ready()
45 }
46 if (file === 'ips') {
47 for await (const [k, v] of ips.iterator())
48 list.add({ ip: k, ...v })
49 return list.ready()
50 }
51 if (file === 'console') {
52 for (const chunk of _.chunk(consoleLog, 1000)) { // avoid occupying the thread too long
53 for (const x of chunk)
54 list.add(x)
55 await wait(0)
56 }
57 ctx.res.once('close', events.on('console', x => list.add(x)))
58 return list.ready()
59 }
60 // for other logs we only provide updates. Use get_log_file to download past content
61 if (_.some(files, x => !_.some(loggers, { name: x })) )
62 return list.error(HTTP_NOT_FOUND, true)
63 list.ready()
64 // unsubscribe when the connection is interrupted
65 ctx.res.once('close', events.on(files, x =>
66 list.add(Object.assign(_.pick(x.ctx, ['ip', 'method','status']), x, { ctx: undefined }))))
67 }
68 })
69
70 },
71
72 async delete_ips({ ip, ts }) {
73 apiAssertTypes({ string_undefined: { ip, ts } })
74 if (ip) {
75 if (!ips.has(ip))
76 throw HTTP_NOT_FOUND
77 ips.del(ip)
78 return {}
79 }
80 if (ts) {
81 ts = new Date(ts)
82 let n = 0
83 for await (const [k, rec] of ips.iterator())
84 if (rec.ts <= ts) {
85 ips.del(k)
86 ++n
87 }
88 return { n }
89 }
90 throw HTTP_BAD_REQUEST
91 },
92
93 reset_ips() {
94 return ips.clear()
95 },
96
97 } satisfies ApiHandlers