Admin: Live-Konsole der Container und Live-Log der Docker-Hosts (nur lesen, Passwörter ausgeblendet)
This commit is contained in:
1 parent
fb96fb5d08
commit
ee9c488a7e
9 files changed
+328
-3
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { execFile, spawn } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import { configureViaRcon, listRoundBackups, loadRoundBackup, rconStatus } from './rconConfigure.js';
|
||||
import { runRcon } from './rcon.js';
|
||||
@@ -33,6 +33,39 @@ export function countSlots(host, used) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ereignisse des Docker-Hosts, auf Deutsch. exec_* (Healthchecks fremder Container) und Mount-Rauschen fallen weg,
|
||||
// weil sie nichts aussagen und Befehlszeilen mit Zugangsdaten enthalten können.
|
||||
const EVENT_LABELS = {
|
||||
container: {
|
||||
create: 'angelegt', start: 'gestartet', restart: 'neu gestartet', stop: 'gestoppt', kill: 'abgebrochen',
|
||||
die: 'beendet', destroy: 'entfernt', pause: 'angehalten', unpause: 'fortgesetzt', oom: 'kein Speicher mehr',
|
||||
'health_status: healthy': 'gesund', 'health_status: unhealthy': 'ungesund',
|
||||
},
|
||||
image: { pull: 'geladen', delete: 'gelöscht', untag: 'Markierung entfernt', tag: 'markiert' },
|
||||
volume: { create: 'angelegt', destroy: 'gelöscht' },
|
||||
};
|
||||
const EVENT_TYPES = { container: 'Container', image: 'Image', volume: 'Volume' };
|
||||
|
||||
/** Eine JSON-Zeile von `docker events` lesbar machen. null = Ereignis wird nicht angezeigt. */
|
||||
export function formatDockerEvent(line) {
|
||||
let e;
|
||||
try {
|
||||
e = JSON.parse(line);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const label = EVENT_LABELS[e.Type]?.[e.Action];
|
||||
if (!label) return null;
|
||||
const attrs = e.Actor?.Attributes ?? {};
|
||||
const time = new Date((e.time ?? 0) * 1000).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
const name = attrs.name ?? e.Actor?.ID?.slice(0, 12) ?? '';
|
||||
const app = attrs['cs2-mapveto'] === '1' ? ' [Mapveto]' : '';
|
||||
const extra = e.Action === 'die' && attrs.exitCode != null ? ` (Exit-Code ${attrs.exitCode})`
|
||||
: e.Action === 'kill' && attrs.signal ? ` (Signal ${attrs.signal})`
|
||||
: e.Type === 'container' && attrs.image ? ` (${attrs.image})` : '';
|
||||
return `${time} ${EVENT_TYPES[e.Type]} ${name}${app} ${label}${extra}`;
|
||||
}
|
||||
|
||||
/** Fehler von "docker run", wenn ein Port auf dem Host schon von etwas anderem belegt ist. */
|
||||
const PORT_BUSY = /port is already allocated|address already in use/i;
|
||||
const DISK_CACHE_MS = 60_000;
|
||||
@@ -278,6 +311,18 @@ export class DockerServerAdapter {
|
||||
return runRcon(this.target(handle), commands);
|
||||
}
|
||||
|
||||
/** Ereignisse des Docker-Hosts live (`docker events`), ab `sinceMinutes` in der Vergangenheit. Nur lesen. */
|
||||
streamEvents({ sinceMinutes = 120 } = {}) {
|
||||
const env = this.options.dockerHost ? { ...process.env, DOCKER_HOST: this.options.dockerHost } : process.env;
|
||||
return spawn(this.options.bin, ['events', '--since', `${Math.max(0, sinceMinutes)}m`, '--format', '{{json .}}'], { env, windowsHide: true });
|
||||
}
|
||||
|
||||
/** Konsolenausgabe des Containers live: `docker logs --follow`. Liefert den Kindprozess (stdout/stderr, kill). */
|
||||
streamLogs(handle, { tail = 200 } = {}) {
|
||||
const env = this.options.dockerHost ? { ...process.env, DOCKER_HOST: this.options.dockerHost } : process.env;
|
||||
return spawn(this.options.bin, ['logs', '--follow', '--tail', String(tail), handle.container], { env, windowsHide: true });
|
||||
}
|
||||
|
||||
async stop(handle) {
|
||||
await this.docker(['rm', '-f', handle.container]).catch(() => {});
|
||||
}
|
||||
@@ -474,6 +519,15 @@ export class DockerHostPool {
|
||||
return this.adapterFor(handle).sendCommands(handle, commands);
|
||||
}
|
||||
|
||||
streamLogs(handle, options) {
|
||||
return this.adapterFor(handle).streamLogs(handle, options);
|
||||
}
|
||||
|
||||
/** Ereignisse eines Hosts nach Namen; null, wenn es den Host nicht gibt. */
|
||||
streamEvents(name, options) {
|
||||
return this.adapters.get(name)?.streamEvents(options) ?? null;
|
||||
}
|
||||
|
||||
async stop(handle) {
|
||||
return this.adapterFor(handle).stop(handle);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user