Improve application responsiveness and background processing
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { pendingHostOperations } from '$lib/server/hosts/index.js';
|
||||
import { pendingHostOperations, waitForHostWork } from '$lib/server/hosts/index.js';
|
||||
import { authenticateAgent } from '$lib/server/hosts/http.js';
|
||||
import { reconcileHostPreparation } from '$lib/server/hosts/preparation.js';
|
||||
export function GET({ request }: { request: Request }) {
|
||||
export async function GET({ request }: { request: Request }) {
|
||||
const host = authenticateAgent(request);
|
||||
reconcileHostPreparation(host.id);
|
||||
return json({ operations: pendingHostOperations(host.id), serverTime: new Date().toISOString() }, { headers: { 'Cache-Control': 'no-store' } });
|
||||
const params = new URL(request.url).searchParams;
|
||||
const lane = params.get('lane') === 'read' ? 'read' : params.get('lane') === 'write' ? 'write' : 'all';
|
||||
let operations = pendingHostOperations(host.id, lane);
|
||||
if (!operations.length && params.get('wait') === '20') {
|
||||
await waitForHostWork(host.id, request.signal);
|
||||
authenticateAgent(request); // Revocation still applies after waiting.
|
||||
operations = pendingHostOperations(host.id, lane);
|
||||
}
|
||||
return json({ operations, serverTime: new Date().toISOString() }, { headers: { 'Cache-Control': 'no-store' } });
|
||||
}
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { serverTitle } from '$lib/ui/server-labels.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
|
||||
export async function GET({ url }: { url: URL }) {
|
||||
const activeOnly = url.searchParams.get('active') === 'true';
|
||||
const historyOnly = url.searchParams.get('view') === 'history';
|
||||
const server = url.searchParams.get('server');
|
||||
const query = (url.searchParams.get('q') ?? '').slice(0, 100).toLowerCase();
|
||||
const limit = Math.min(500, Math.max(1, Math.floor(Number(url.searchParams.get('limit')) || 25)));
|
||||
const offset = Math.min(1_000_000, Math.max(0, Math.floor(Number(url.searchParams.get('offset')) || 0)));
|
||||
const active = `EXISTS (SELECT 1 FROM deployment_servers a WHERE a.deployment_id=d.id AND a.server_deleted_at IS NULL AND a.restart_state IN ('pending','failed'))`;
|
||||
const names = query ? (db().prepare('SELECT DISTINCT server FROM deployment_servers').all() as { server: string }[])
|
||||
.map(row => row.server).filter(name => `${name} ${serverTitle(name)}`.toLowerCase().includes(query)) : [];
|
||||
const where = `(? IS NULL OR EXISTS (SELECT 1 FROM deployment_servers target WHERE target.deployment_id=d.id AND target.server=?))
|
||||
${activeOnly ? `AND ${active}` : historyOnly ? `AND NOT ${active}` : ''}
|
||||
${query ? `AND EXISTS (SELECT 1 FROM deployment_servers target WHERE target.deployment_id=d.id AND target.server IN (${names.map(() => '?').join(',') || 'NULL'}))` : ''}`;
|
||||
const parameters = [server, server, ...names];
|
||||
const { total } = db().prepare(`SELECT COUNT(*) AS total FROM deployments d WHERE ${where}`).get(...parameters) as { total: number };
|
||||
|
||||
const deployments = db()
|
||||
.prepare(
|
||||
@@ -15,11 +28,11 @@ export async function GET({ url }: { url: URL }) {
|
||||
LEFT JOIN scripts s ON s.id = d.script_id
|
||||
LEFT JOIN script_versions sv ON sv.id = d.script_version_id
|
||||
LEFT JOIN script_versions pv ON pv.id = d.prior_version_id
|
||||
WHERE (? IS NULL OR EXISTS (SELECT 1 FROM deployment_servers target WHERE target.deployment_id = d.id AND target.server = ?))
|
||||
ORDER BY d.started_at DESC
|
||||
LIMIT 500`
|
||||
WHERE ${where}
|
||||
ORDER BY d.started_at DESC, d.rowid DESC
|
||||
LIMIT ? OFFSET ?`
|
||||
)
|
||||
.all(server, server) as Array<{
|
||||
.all(...parameters, limit, offset) as Array<{
|
||||
id: string;
|
||||
action: string;
|
||||
started_at: string;
|
||||
@@ -36,7 +49,7 @@ export async function GET({ url }: { url: URL }) {
|
||||
}>;
|
||||
|
||||
const ids = deployments.map(deployment => deployment.id);
|
||||
if (!ids.length) return json({ deployments: [] });
|
||||
if (!ids.length) return json({ deployments: [], total, limit, offset });
|
||||
const placeholders = ids.map(() => '?').join(',');
|
||||
const serversByDep = db()
|
||||
.prepare(
|
||||
@@ -95,5 +108,5 @@ export async function GET({ url }: { url: URL }) {
|
||||
}))
|
||||
.filter((d) => (activeOnly ? d.servers.some((s) => !s.server_deleted_at && (s.restart_state === 'pending' || s.restart_state === 'failed')) : true));
|
||||
|
||||
return json({ deployments: rows });
|
||||
return json({ deployments: rows, total, limit, offset });
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function GET() {
|
||||
event.type === 'server-state' ||
|
||||
event.type === 'deployment-opened' ||
|
||||
event.type === 'server-changed' ||
|
||||
event.type === 'sweep-tick'
|
||||
event.type === 'sweep-tick' || event.type === 'fleet-invalidated'
|
||||
) {
|
||||
send(controller, event);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { observationReader } from '$lib/server/fleet/status.js';
|
||||
import { listServers } from '$lib/server/fleet/servers.js';
|
||||
import { statusEntry } from '$lib/server/pipeline/worker.js';
|
||||
import { subscribe } from '$lib/server/pipeline/bus.js';
|
||||
import { listTelemetry } from '$lib/server/fleet/telemetry.js';
|
||||
|
||||
@@ -15,7 +18,10 @@ export async function GET() {
|
||||
controller.enqueue(encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify({ ...event, serverNow: Date.now() })}\n\n`));
|
||||
}
|
||||
});
|
||||
const observation = observationReader();
|
||||
const observe = (server: ReturnType<typeof listServers>[number]) => statusEntry(server, observation);
|
||||
controller.enqueue(encoder.encode(': hello\n\n'));
|
||||
controller.enqueue(encoder.encode(`event: server-status\ndata: ${JSON.stringify({ entries: listServers().map(server => observe(server)), snapshot: true, serverNow: Date.now() })}\n\n`));
|
||||
controller.enqueue(encoder.encode(`event: server-telemetry\ndata: ${JSON.stringify({ entries: listTelemetry(), snapshot: true, serverNow: Date.now() })}\n\n`));
|
||||
heartbeat = setInterval(() => controller.enqueue(encoder.encode(': ping\n\n')), HEARTBEAT_MS);
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ export function GET({ request, params }: Event) {
|
||||
requireManagementOrigin(request); const host = getHost(params.id); if (!host) error(404, 'Host not found.');
|
||||
// Payloads and results can contain RCON keys or staged credentials. History
|
||||
// exposes metadata only; agent APIs remain the only command delivery path.
|
||||
return json({ host: presentHostPreparation(host), defaultRuntimeImage: config.komodo.defaultImage, deletion: hostDeletionStatus(host.id), operations: listHostOperations(host.id).map(({ payload: _payload, result: _result, ...operation }) => operation) });
|
||||
return json({ host: presentHostPreparation(host), defaultRuntimeImage: config.komodo.defaultImage, deletion: hostDeletionStatus(host.id), operations: listHostOperations(host.id, 100, true).map(({ payload: _payload, result: _result, ...operation }) => operation) });
|
||||
}
|
||||
export async function PATCH({ request, params }: Event) {
|
||||
requireManagementOrigin(request);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { leaderboardReport } from '$lib/server/reports/index.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getLeaderboard, isLeaderboardMetric, MAX_LEADERBOARD_OFFSET } from '$lib/server/fleet/leaderboard.js';
|
||||
import { isLeaderboardMetric, MAX_LEADERBOARD_OFFSET } from '$lib/server/fleet/leaderboard.js';
|
||||
|
||||
export function GET({ url }: { url: URL }) {
|
||||
export async function GET({ url }: { url: URL }) {
|
||||
const params = url.searchParams;
|
||||
const game = params.get('game');
|
||||
const map = params.get('map');
|
||||
@@ -20,7 +21,7 @@ export function GET({ url }: { url: URL }) {
|
||||
|| !Number.isFinite(offset) || offset < 0 || offset > MAX_LEADERBOARD_OFFSET) {
|
||||
return json({ ok: false, kind: 'bad-request', error: 'Use game t5 or t6, a supported metric, map key up to 1024 characters, q up to 100 characters, limit 1–100, and offset 0–1000000.' }, { status: 400 });
|
||||
}
|
||||
return json(getLeaderboard({ game: game ?? undefined, map: map || undefined,
|
||||
return json(await leaderboardReport({ game: game ?? undefined, map: map || undefined,
|
||||
metric: metric !== null && isLeaderboardMetric(metric) ? metric : undefined,
|
||||
q: q ?? undefined, limit, offset }));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { parseMetricsQuery } from '$lib/metrics.js';
|
||||
import { readMetrics } from '$lib/server/fleet/metrics.js';
|
||||
import { metricsReport } from '$lib/server/reports/index.js';
|
||||
|
||||
export function GET({ url }: { url: URL }) {
|
||||
return json(readMetrics(parseMetricsQuery(url.searchParams)), {
|
||||
export async function GET({ url }: { url: URL }) {
|
||||
return json(await metricsReport(parseMetricsQuery(url.searchParams)), {
|
||||
headers: { 'cache-control': 'no-store' }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,48 +1,10 @@
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { fleetSnapshot } from '$lib/server/fleet/snapshot.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { listServers, heldByBeta } from '$lib/server/fleet/servers.js';
|
||||
import { serverObservation } from '$lib/server/fleet/status.js';
|
||||
import { queueCreateServer } from '$lib/server/provisioning/index.js';
|
||||
import { config, komodoConfigured, rconConfigured } from '$lib/server/config.js';
|
||||
import type { GameMode, ZombieGame } from '$lib/server/fleet/map-aliases.js';
|
||||
import { runningVersions } from '$lib/server/integrations/plutonium.js';
|
||||
import { listHosts } from '$lib/server/hosts/index.js';
|
||||
import { nativeInstance } from '$lib/server/backends/native.js';
|
||||
|
||||
export async function GET() {
|
||||
const rconHost = config.rcon.host;
|
||||
const hosts = listHosts();
|
||||
const hostsById = new Map(hosts.map(host => [host.id, host]));
|
||||
const versions = new Map(runningVersions().map(version => [version.server, version]));
|
||||
const servers = listServers().map((s) => {
|
||||
const instance = s.native_instance_id ? nativeInstance(s.name) : null;
|
||||
return {
|
||||
...serverObservation(s),
|
||||
host: s.host_id ? hostsById.get(s.host_id) ?? null : null,
|
||||
network: instance ? { bindAddress: instance.bind_address, localPort: instance.port, publicAddress: instance.public_address, publicPort: instance.public_port } : null,
|
||||
plutonium: versions.get(s.name),
|
||||
// Derived endpoint — same logic as endpointFor() but without asking
|
||||
// Komodo. If we don't have a port yet, endpoint is null.
|
||||
endpoint: instance ? { host: `agent:${instance.host_id}`, port: instance.port } : s.game_port ? { host: s.rcon_host ?? rconHost, port: s.game_port } : null,
|
||||
held: [...heldByBeta(s.name).entries()].map(([target, betaId]) => ({ target, betaId }))
|
||||
};
|
||||
});
|
||||
return json({
|
||||
servers,
|
||||
serverNow: Date.now(),
|
||||
reservedNames: (db().prepare("SELECT j.server FROM server_creation_jobs j JOIN server_deployments d ON d.id = j.deployment_id WHERE d.state IN ('queued', 'running', 'awaiting-health')").all() as { server: string }[]).map(row => row.server),
|
||||
integrations: {
|
||||
native: hosts.length > 0 || !komodoConfigured(),
|
||||
hostsReady: hosts.filter(host => host.readiness === 'ready').length,
|
||||
hostsTotal: hosts.length,
|
||||
rcon: rconConfigured(),
|
||||
komodo: komodoConfigured(),
|
||||
rconTimeoutMs: config.rcon.timeoutMs,
|
||||
defaultRconHost: rconHost
|
||||
}
|
||||
});
|
||||
}
|
||||
export function GET() { return json(fleetSnapshot()); }
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
|
||||
Reference in New Issue
Block a user