Add persistence, lifecycle improvements, logging and typescript fixes
This commit is contained in:
+10
-2
@@ -7,6 +7,7 @@ import { PortManager } from "./portManager";
|
|||||||
import { ServerStatusManager } from "./serverStatusManager";
|
import { ServerStatusManager } from "./serverStatusManager";
|
||||||
import { ServerLifecycle } from "./serverLifecycle";
|
import { ServerLifecycle } from "./serverLifecycle";
|
||||||
import { getLogger } from "./logger";
|
import { getLogger } from "./logger";
|
||||||
|
import { createWebServer } from "./web/server";
|
||||||
|
|
||||||
const logger = getLogger("Index");
|
const logger = getLogger("Index");
|
||||||
|
|
||||||
@@ -103,11 +104,18 @@ async function main(){
|
|||||||
|
|
||||||
await statusManager.start();
|
await statusManager.start();
|
||||||
|
|
||||||
|
const webServer = createWebServer({
|
||||||
|
wrapperRegistry,
|
||||||
|
serverRegistry,
|
||||||
|
lifecycle,
|
||||||
|
rabbitMqStatus: "connected",
|
||||||
|
managerStatus: "running"
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
logger.info("Manager gestartet");
|
logger.info("Manager gestartet");
|
||||||
|
|
||||||
|
void webServer;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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" }));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user