@ -0,0 +1,66 @@
|
|||||||
|
import type { FastifyInstance, FastifyRequest } from 'fastify'
|
||||||
|
import type { WebSocket } from 'ws'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _terminal Routes
|
||||||
|
*
|
||||||
|
* The websocket behind the admin area's terminal view, which streams this instance's log lines to a
|
||||||
|
* browser as they are written. Read-only: nothing a client sends is looked at, and the socket carries
|
||||||
|
* exactly what `core/logger.ts` hands to `console.log`, formatted string and all — including the ANSI
|
||||||
|
* colours, which xterm renders.
|
||||||
|
*
|
||||||
|
* Only this instance's own main thread is on that stream. Worker threads build their own logger
|
||||||
|
* (`worker.ts`), and other instances write to their own consoles, so a clustered deployment shows the
|
||||||
|
* terminal of whichever instance the socket happened to land on.
|
||||||
|
*
|
||||||
|
* Log lines quote paths, e-mail addresses and query failures, so the handshake takes `manage:system`
|
||||||
|
* — the same permission as the rest of the system views, and never granted to a group by accident.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How much unsent traffic a client may accumulate before its stream starts skipping lines.
|
||||||
|
*
|
||||||
|
* A browser that has stopped reading — a backgrounded tab on a slow link, most likely — would
|
||||||
|
* otherwise have the server hold every line since it stalled. Dropping is right here: the terminal is
|
||||||
|
* a live view of what is happening now, not a transcript that has to be complete.
|
||||||
|
*/
|
||||||
|
const MAX_BUFFERED = 1048576 // 1mb
|
||||||
|
|
||||||
|
async function routes(app: FastifyInstance) {
|
||||||
|
app.get(
|
||||||
|
'/logs',
|
||||||
|
{ websocket: true, schema: { hide: true } },
|
||||||
|
(socket: WebSocket, req: FastifyRequest) => {
|
||||||
|
/*
|
||||||
|
Refusals close the socket with a code in the private 4000 range, where the browser hands both
|
||||||
|
code and reason to the page — which is how the terminal can print why it was turned away and
|
||||||
|
know not to offer a reconnect. See `pages/AdminTerminal.vue`.
|
||||||
|
*/
|
||||||
|
if (!req.session?.authenticated) {
|
||||||
|
return socket.close(4401, 'Authentication is required')
|
||||||
|
}
|
||||||
|
if (!req.session.permissions?.includes('manage:system')) {
|
||||||
|
return socket.close(4403, 'You are not allowed to read the server logs')
|
||||||
|
}
|
||||||
|
|
||||||
|
const send = (line: string) => {
|
||||||
|
if (socket.readyState !== socket.OPEN || socket.bufferedAmount > MAX_BUFFERED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socket.send(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> A terminal that opens onto an idle server would otherwise sit empty and look broken
|
||||||
|
for (const line of WIKI.logger.backlog()) {
|
||||||
|
send(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
WIKI.logger.ws.on('log', send)
|
||||||
|
socket.on('close', () => {
|
||||||
|
WIKI.logger.ws.off('log', send)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default routes
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 711 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 9.8 KiB |