Add emulator stats dashboard and refresh classic UI views

This commit is contained in:
Lorenzune
2026-05-25 10:10:40 +02:00
parent 4e1ceed53f
commit b038ca4542
38 changed files with 2476 additions and 336 deletions
+198
View File
@@ -0,0 +1,198 @@
import { GetConfiguration } from '@nitrots/nitro-renderer';
import { getAccessToken } from '../auth';
export interface EmuStatsOverview
{
uptimeSeconds: number;
lastRefreshEpochMs: number;
guiStatus: string;
memoryUsedMb: number;
memoryMaxMb: number;
memoryAllocatedMb: number;
memoryUsagePercent: number;
cpuLoadPercent: number;
activeOsThreads: number;
connectedPlayers: number;
loadedRooms: number;
wiredTickables: number;
peakPlayers: number;
activeWebSocketSessions: number;
peakWebSocketSessions: number;
averageRoomCycleMs: number;
worstRoomCycleMs: number;
worstRoomCycleRoomId: number;
worstRoomCycleRoomName: string;
delayedEventsPending: number;
overloadedWiredRooms: number;
heavyWiredRooms: number;
wiredActivityPerSecond: number;
}
export interface EmuStatsMemoryPoint
{
timestamp: number;
usedMb: number;
maxMb: number;
usagePercent: number;
}
export interface EmuStatsUserRow
{
id: number;
username: string;
rank: string;
credits: number;
roomId: number;
}
export interface EmuStatsRoomRow
{
roomId: number;
name: string;
players: number;
items: number;
tickables: number;
cpuMs: number;
estimatedRamKb: number;
thread: string;
}
export interface EmuStatsWiredRow
{
roomId: number;
averageTickMs: number;
peakTickMs: number;
usagePercent: number;
delayedEventsPending: number;
overloaded: boolean;
heavy: boolean;
}
export interface EmuStatsWiredTopRoomRow
{
roomId: number;
name: string;
usagePercent: number;
averageTickMs: number;
peakTickMs: number;
delayedEventsPending: number;
activityPerSecond: number;
heavy: boolean;
}
export interface EmuStatsDatabasePool
{
activeConnections: number;
idleConnections: number;
totalConnections: number;
waitingThreads: number;
maxConnections: number;
}
export interface EmuStatsScheduler
{
queuedTasks: number;
activeThreads: number;
poolSize: number;
completedTasks: number;
running: boolean;
}
export interface EmuStatsNetwork
{
incomingPacketsPerSecond: number;
outgoingPacketsPerSecond: number;
incomingKilobytesPerSecond: number;
outgoingKilobytesPerSecond: number;
totalIncomingPackets: number;
totalOutgoingPackets: number;
}
export interface EmuStatsGarbageCollector
{
totalCollections: number;
totalCollectionTimeMs: number;
collectionsSinceLastSample: number;
lastObservedPauseMs: number;
sampledAtEpochMs: number;
}
export interface EmuStatsSnapshot
{
overview: EmuStatsOverview;
memoryHistory: EmuStatsMemoryPoint[];
users: EmuStatsUserRow[];
rooms: EmuStatsRoomRow[];
wired: EmuStatsWiredRow[];
wiredTopRooms: EmuStatsWiredTopRoomRow[];
databasePool: EmuStatsDatabasePool;
scheduler: EmuStatsScheduler;
network: EmuStatsNetwork;
garbageCollector: EmuStatsGarbageCollector;
}
const interpolate = (value: string): string =>
{
try { return GetConfiguration().interpolate(value); }
catch { return value; }
};
const getUrl = (): string =>
{
const configured = GetConfiguration().getValue<string>('emustats.endpoint', '${api.url}/api/emustats');
return interpolate(configured);
};
const buildHeaders = (): Record<string, string> =>
{
const headers: Record<string, string> = {
Accept: 'application/json',
'X-Requested-With': 'NitroEmuStats'
};
const token = getAccessToken();
if(token) headers.Authorization = `Bearer ${ token }`;
return headers;
};
let cacheValue: EmuStatsSnapshot = null;
const parseJson = async <T>(response: Response): Promise<T> =>
{
const text = await response.text();
if(!text) return {} as T;
try { return JSON.parse(text) as T; }
catch { throw new Error('Invalid emulator stats response.'); }
};
export const fetchEmuStats = async (force = false): Promise<EmuStatsSnapshot> =>
{
if(!force && cacheValue) return cacheValue;
const response = await fetch(getUrl(), {
method: 'GET',
credentials: 'include',
headers: buildHeaders()
});
const payload = await parseJson<EmuStatsSnapshot & { error?: string }>(response);
if(!response.ok)
{
throw new Error(payload?.error || `Request failed (${ response.status }).`);
}
cacheValue = payload;
return payload;
};
export const getCachedEmuStats = (): EmuStatsSnapshot =>
{
return cacheValue;
};
+1
View File
@@ -0,0 +1 @@
export * from './EmuStatsApi';
+1
View File
@@ -8,6 +8,7 @@ export * from './camera';
export * from './campaign';
export * from './catalog';
export * from './chat-history';
export * from './emustats';
export * from './events';
export * from './friends';
export * from './groups';