| 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 |