Add persistence, lifecycle improvements, logging and typescript fixes

This commit is contained in:
2026-07-21 14:49:09 +02:00
parent 4855bb882c
commit 6d22275a1f
4 changed files with 233 additions and 2 deletions
+10 -2
View File
@@ -7,6 +7,7 @@ import { PortManager } from "./portManager";
import { ServerStatusManager } from "./serverStatusManager";
import { ServerLifecycle } from "./serverLifecycle";
import { getLogger } from "./logger";
import { createWebServer } from "./web/server";
const logger = getLogger("Index");
@@ -103,11 +104,18 @@ async function main(){
await statusManager.start();
const webServer = createWebServer({
wrapperRegistry,
serverRegistry,
lifecycle,
rabbitMqStatus: "connected",
managerStatus: "running"
}, 3000);
logger.info("Manager gestartet");
void webServer;
}
+140
View File
@@ -0,0 +1,140 @@
import { ServerRegistry } from "../serverRegistry";
import { WrapperRegistry } from "../wrapperRegistry";
import { ServerLifecycle } from "../serverLifecycle";
interface DashboardSummary {
managerStatus: string;
rabbitMqStatus: string;
wrapperCount: number;
runningServerCount: number;
totalPlayers: number;
totalCapacity: number;
utilizationPercent: number;
desiredServers: number;
activeServers: number;
}
export interface WebApiDependencies {
wrapperRegistry: WrapperRegistry;
serverRegistry: ServerRegistry;
lifecycle: ServerLifecycle;
rabbitMqStatus?: string;
managerStatus?: string;
}
export class WebApi {
constructor(private deps: WebApiDependencies) {}
getHealth() {
return {
status: "ok"
};
}
getDashboard(): DashboardSummary {
const wrappers = this.deps.wrapperRegistry.getAll();
const servers = this.deps.serverRegistry.getAll();
const runningServers = servers.filter(server => server.status === "RUNNING").length;
const totalPlayers = servers.reduce((sum, server) => sum + server.players, 0);
const totalCapacity = servers.reduce((sum, server) => sum + server.maxPlayers, 0);
const utilizationPercent = totalCapacity > 0 ? Math.round((totalPlayers / totalCapacity) * 100) : 0;
return {
managerStatus: this.deps.managerStatus ?? "running",
rabbitMqStatus: this.deps.rabbitMqStatus ?? "connected",
wrapperCount: wrappers.filter(wrapper => wrapper.online).length,
runningServerCount: runningServers,
totalPlayers,
totalCapacity,
utilizationPercent,
desiredServers: this.getDesiredServers(),
activeServers: runningServers
};
}
getWrappers() {
return this.deps.wrapperRegistry.getAll().map(wrapper => ({
id: wrapper.id,
hostname: wrapper.hostname,
online: wrapper.online,
cpu: wrapper.cpu,
ram: wrapper.ram,
ramFree: wrapper.ramFree,
lastHeartbeat: wrapper.lastHeartbeat,
serverCount: wrapper.servers.length
}));
}
getWrapper(id: string) {
const wrapper = this.deps.wrapperRegistry.get(id);
if (!wrapper) {
return null;
}
return {
id: wrapper.id,
hostname: wrapper.hostname,
online: wrapper.online,
cpu: wrapper.cpu,
ram: wrapper.ram,
ramFree: wrapper.ramFree,
lastHeartbeat: wrapper.lastHeartbeat,
serverCount: wrapper.servers.length,
servers: wrapper.servers.map(server => ({
id: server.id,
name: server.name,
port: server.port,
queryPort: server.queryPort,
players: server.players,
maxPlayers: server.maxPlayers,
alive: server.alive,
started: server.started
}))
};
}
getServers() {
return this.deps.serverRegistry.getAll().map(server => ({
id: server.id,
name: server.name,
status: server.status,
wrapperId: server.wrapperId,
port: server.port,
queryPort: server.queryPort,
players: server.players,
maxPlayers: server.maxPlayers,
created: server.created,
startedAt: server.startedAt,
emptySince: server.emptySince
}));
}
getServer(id: string) {
const server = this.deps.serverRegistry.get(id);
if (!server) {
return null;
}
return {
id: server.id,
name: server.name,
status: server.status,
wrapperId: server.wrapperId,
port: server.port,
queryPort: server.queryPort,
players: server.players,
maxPlayers: server.maxPlayers,
created: server.created,
startedAt: server.startedAt,
emptySince: server.emptySince
};
}
private getDesiredServers(): number {
const lifecycle = this.deps.lifecycle as ServerLifecycle & { desiredServers?: number };
return lifecycle.desiredServers ?? 0;
}
}
+67
View File
@@ -0,0 +1,67 @@
import { IncomingMessage, ServerResponse } from "http";
import { parse as parseUrl } from "url";
import { WebApi } from "./api";
export function registerRoutes(server: { on: (event: string, listener: (...args: any[]) => void) => void }, api: WebApi): void {
server.on("request", (req: IncomingMessage, res: ServerResponse) => {
const url = parseUrl(req.url ?? "/", true);
const pathname = url.pathname ?? "/";
if (pathname === "/api/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(api.getHealth()));
return;
}
if (pathname === "/api/dashboard") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(api.getDashboard()));
return;
}
if (pathname === "/api/wrappers") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(api.getWrappers()));
return;
}
if (pathname.startsWith("/api/wrappers/")) {
const id = pathname.split("/").pop() ?? "";
const wrapper = api.getWrapper(id);
if (!wrapper) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Wrapper not found" }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(wrapper));
return;
}
if (pathname === "/api/servers") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(api.getServers()));
return;
}
if (pathname.startsWith("/api/servers/")) {
const id = pathname.split("/").pop() ?? "";
const server = api.getServer(id);
if (!server) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Server not found" }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(server));
return;
}
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not found" }));
});
}
+16
View File
@@ -0,0 +1,16 @@
import { createServer, IncomingMessage, ServerResponse } from "http";
import { registerRoutes } from "./routes";
import { WebApi, WebApiDependencies } from "./api";
export function createWebServer(deps: WebApiDependencies, port = 3000) {
const api = new WebApi(deps);
const server = createServer();
registerRoutes(server, api);
server.listen(port, () => {
// no-op: startup is handled by the manager
});
return server;
}