Enhance broadcast.js with comprehensive statistics reporting
- Introduced a new function to read server start logs, providing insights into server uptime and usage. - Refactored the statistics reporting to include detailed metrics from live server connections, start logs, and login records. - Improved the presentation of user statistics, including unique users, login timestamps, and demographic data, enhancing overall clarity and usability of the statistics command. - Updated command descriptions for better understanding of the new functionality. These changes collectively enrich the statistics reporting capabilities, offering a more complete view of server and user activity.
This commit is contained in:
@@ -362,31 +362,135 @@ export function setupBroadcast(io, __dirname) {
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
function buildAllStats(records) {
|
||||
if (records.length === 0) {
|
||||
return ['Keine Login-Daten vorhanden.'];
|
||||
function readStartsLogStats() {
|
||||
const path = join(ensureLogsDir(__dirname), 'starts.log');
|
||||
if (!existsSync(path)) {
|
||||
return { count: 0, first: null, last: null };
|
||||
}
|
||||
const lines = readFileSync(path, 'utf-8')
|
||||
.split('\n')
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean);
|
||||
if (lines.length === 0) {
|
||||
return { count: 0, first: null, last: null };
|
||||
}
|
||||
return { count: lines.length, first: lines[0], last: lines[lines.length - 1] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sammelt alle verfügbaren Kennzahlen: Live-Server, starts.log, vollständige Auswertung von logins.log
|
||||
* (entspricht inhaltlich den /stat-Unterbefehlen in einer Tabelle).
|
||||
*/
|
||||
function buildFullAllStatsRows(records) {
|
||||
const rows = [];
|
||||
const push = (a, b) => rows.push([String(a), b == null ? '' : String(b)]);
|
||||
|
||||
const liveOnline = Array.from(clients.values()).filter(
|
||||
(c) => c.userName && c.socket && c.socket.connected
|
||||
).length;
|
||||
const socketCount =
|
||||
io.sockets && io.sockets.sockets && typeof io.sockets.sockets.size === 'number'
|
||||
? io.sockets.sockets.size
|
||||
: 0;
|
||||
let totalMessages = 0;
|
||||
for (const msgs of conversations.values()) {
|
||||
totalMessages += Array.isArray(msgs) ? msgs.length : 0;
|
||||
}
|
||||
|
||||
const starts = readStartsLogStats();
|
||||
|
||||
push('— Live (Server) —', '');
|
||||
push('Nutzer mit Chat-Login und aktiver Verbindung', liveOnline);
|
||||
push('Socket.IO-Verbindungen (/)', socketCount);
|
||||
push('Client-Sitzungen im Speicher', clients.size);
|
||||
push('Unterhaltungen im Speicher (Paare)', conversations.size);
|
||||
push('Nachrichten-Einträge gesamt (RAM)', totalMessages);
|
||||
push('Server-Starts protokolliert (starts.log)', starts.count);
|
||||
if (starts.first) {
|
||||
push('Erster Start (Timestamp)', starts.first);
|
||||
}
|
||||
if (starts.last) {
|
||||
push('Letzter Start (Timestamp)', starts.last);
|
||||
}
|
||||
|
||||
push('— Login-Protokoll (logins.log, UTC) —', '');
|
||||
if (records.length === 0) {
|
||||
push('Keine Einträge', '—');
|
||||
return rows;
|
||||
}
|
||||
|
||||
const sortedByDate = records.slice().sort((a, b) => a.date - b.date);
|
||||
const firstTs = sortedByDate[0].timestamp;
|
||||
const lastTs = sortedByDate[sortedByDate.length - 1].timestamp;
|
||||
const daysSorted = records.map((r) => r.day).sort();
|
||||
const dayMin = daysSorted[0];
|
||||
const dayMax = daysSorted[daysSorted.length - 1];
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const todayCount = records.filter((r) => r.day === today).length;
|
||||
const dayRecords = records.filter((r) => r.day === today);
|
||||
const latestByUserToday = new Map();
|
||||
dayRecords
|
||||
.slice()
|
||||
.sort((a, b) => b.date - a.date)
|
||||
.forEach((record) => {
|
||||
if (!latestByUserToday.has(record.userName)) {
|
||||
latestByUserToday.set(record.userName, record);
|
||||
}
|
||||
});
|
||||
const uniqueToday = latestByUserToday.size;
|
||||
const namesToday = Array.from(latestByUserToday.keys()).sort((a, b) => a.localeCompare(b, 'de'));
|
||||
|
||||
const uniqueNames = new Set(records.map((r) => r.userName)).size;
|
||||
const ages = records.map((r) => r.age);
|
||||
const minAge = Math.min(...ages);
|
||||
const maxAge = Math.max(...ages);
|
||||
const youngest = records.find((r) => r.age === minAge);
|
||||
const oldest = records.find((r) => r.age === maxAge);
|
||||
const topCountries = aggregateTop(records, (r) => r.country, 5)
|
||||
.map(([name, count]) => `${name}(${count})`)
|
||||
.join(', ');
|
||||
|
||||
return [
|
||||
`Logins gesamt: ${records.length}`,
|
||||
`Logins heute (${today}): ${todayCount}`,
|
||||
`Unterschiedliche Namen: ${uniqueNames}`,
|
||||
`Jüngster Nutzer: ${youngest.userName} (${youngest.age})`,
|
||||
`Ältester Nutzer: ${oldest.userName} (${oldest.age})`,
|
||||
`Top Länder: ${topCountries || 'keine'}`
|
||||
];
|
||||
push('Zeitraum (Kalendertage)', `${dayMin} … ${dayMax}`);
|
||||
push('Erster Login (Timestamp)', firstTs);
|
||||
push('Letzter Login (Timestamp)', lastTs);
|
||||
push('Logins gesamt', records.length);
|
||||
push(`Logins heute (${today} UTC)`, dayRecords.length);
|
||||
push('Verschiedene Nutzer heute (UTC)', uniqueToday);
|
||||
const listToday = namesToday.join(', ');
|
||||
push(
|
||||
'Nutzer heute (Liste)',
|
||||
listToday.length > 500
|
||||
? `${uniqueToday} Nutzer — Auszug: ${namesToday.slice(0, 8).join(', ')} … (vollständig: /stat today)`
|
||||
: listToday || '—'
|
||||
);
|
||||
push('Verschiedene Namen (historisch)', uniqueNames);
|
||||
push('Ø Logins pro Namen (historisch)', (records.length / uniqueNames).toFixed(2));
|
||||
push('Jüngster Nutzer (erster Treffer im Log)', `${youngest.userName} (${youngest.age})`);
|
||||
push('Ältester Nutzer (erster Treffer im Log)', `${oldest.userName} (${oldest.age})`);
|
||||
|
||||
const topGenders = aggregateTop(
|
||||
records,
|
||||
(r) => (r.gender || '').trim() || '?',
|
||||
15
|
||||
);
|
||||
push('Geschlecht (Häufigkeit)', topGenders.map(([g, n]) => `${g}(${n})`).join(', ') || '—');
|
||||
|
||||
const topCountries = aggregateTop(records, (r) => r.country, 20);
|
||||
push('Top 20 Länder', topCountries.map(([c, n]) => `${c}(${n})`).join(', ') || '—');
|
||||
|
||||
const topNames = aggregateTop(records, (r) => r.userName, 20);
|
||||
const topNamesDetail = topNames
|
||||
.map(([name, count]) => {
|
||||
const latestRecord = records
|
||||
.filter((r) => r.userName === name)
|
||||
.sort((a, b) => b.date - a.date)[0];
|
||||
return `${name}(${count}, zuletzt ${latestRecord.timestamp})`;
|
||||
})
|
||||
.join('; ');
|
||||
push(
|
||||
'Top 20 Namen (Anzahl, letzter Login)',
|
||||
topNamesDetail.length > 1200 ? `${topNamesDetail.slice(0, 1197)}…` : topNamesDetail
|
||||
);
|
||||
push('Hinweis', 'Tabellen je Tag/Zeitraum: /stat today | /stat date | /stat range');
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function executeStatsCommand(socket, client, parts) {
|
||||
@@ -411,7 +515,7 @@ export function setupBroadcast(io, __dirname) {
|
||||
['/stat ages', 'Jüngster und ältester Nutzer'],
|
||||
['/stat names', 'Häufigkeit der verwendeten Namen'],
|
||||
['/stat countries', 'Häufigkeit der Länder'],
|
||||
['/all-stats', 'Kurzübersicht nur aus Logindatei (logins.log), keine Live-Chat-Metriken']
|
||||
['/all-stats', 'Alle Kennzahlen: Live-Server + starts.log + komplette logins.log-Auswertung']
|
||||
]);
|
||||
return;
|
||||
}
|
||||
@@ -513,15 +617,8 @@ export function setupBroadcast(io, __dirname) {
|
||||
sendCommandResult(socket, 'Keine Berechtigung: Recht "stat" fehlt.');
|
||||
return;
|
||||
}
|
||||
const lines = buildAllStats(readLoginRecords());
|
||||
const rows = lines.map((line) => {
|
||||
const separatorIndex = line.indexOf(':');
|
||||
if (separatorIndex === -1) {
|
||||
return [line, ''];
|
||||
}
|
||||
return [line.slice(0, separatorIndex).trim(), line.slice(separatorIndex + 1).trim()];
|
||||
});
|
||||
sendCommandTable(socket, 'Statistik: Übersicht', ['Metrik', 'Wert'], rows);
|
||||
const rows = buildFullAllStatsRows(readLoginRecords());
|
||||
sendCommandTable(socket, 'Statistik: Vollständige Übersicht (/all-stats)', ['Metrik', 'Wert'], rows);
|
||||
}
|
||||
|
||||
function executeKickCommand(socket, client, parts) {
|
||||
@@ -665,7 +762,7 @@ export function setupBroadcast(io, __dirname) {
|
||||
['/logout-admin', 'Admin-/Command-Login beenden'],
|
||||
['/whoami-rights', 'Aktuelle Admin-Rechte anzeigen'],
|
||||
['/stat help', 'Hilfe zu Statistikbefehlen anzeigen'],
|
||||
['/all-stats', 'Kurzübersicht nur aus Logindatei (logins.log), keine Live-Chat-Metriken'],
|
||||
['/all-stats', 'Alle Kennzahlen: Live-Server + starts.log + komplette logins.log-Auswertung'],
|
||||
['/kick <username>', 'Benutzer aus dem Chat werfen'],
|
||||
['/help oder /?', 'Diese Hilfe anzeigen']
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user