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 { 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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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