Debug and inventory

This commit is contained in:
erik 2025-06-19 17:46:19 +00:00
parent 1febf6e918
commit 80a0a16bab
15 changed files with 2764 additions and 341 deletions

654
static/debug.html Normal file
View file

@ -0,0 +1,654 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Player Debug - Dereth Tracker</title>
<style>
body {
font-family: "Segoe UI", sans-serif;
background: #111;
color: #eee;
margin: 20px;
line-height: 1.4;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #333;
padding-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: #222;
border: 1px solid #444;
border-radius: 8px;
padding: 15px;
}
.stat-card h3 {
margin: 0 0 10px 0;
color: #88f;
font-size: 1.1rem;
}
.stat-value {
font-size: 1.8rem;
font-weight: bold;
color: #fff;
}
.sections {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
.section {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 20px;
}
.section h2 {
margin: 0 0 15px 0;
color: #88f;
border-bottom: 1px solid #333;
padding-bottom: 10px;
}
.event-log {
max-height: 400px;
overflow-y: auto;
background: #000;
border: 1px solid #333;
border-radius: 4px;
padding: 10px;
font-family: monospace;
font-size: 0.9rem;
}
.event-enter {
color: #4f4;
}
.event-exit {
color: #f44;
}
.flapping-player {
background: #332;
border: 1px solid #664;
border-radius: 4px;
padding: 10px;
margin-bottom: 10px;
}
.flapping-player .name {
font-weight: bold;
color: #ff6;
}
.flapping-player .score {
color: #f88;
font-size: 0.9rem;
}
.history-entry {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #333;
font-family: monospace;
font-size: 0.9rem;
}
.history-entry:last-child {
border-bottom: none;
}
.timestamp {
color: #888;
font-size: 0.8rem;
}
.player-count {
font-weight: bold;
color: #4f4;
}
.status {
text-align: center;
padding: 20px;
background: #222;
border-radius: 8px;
margin-bottom: 20px;
}
.loading {
color: #88f;
}
.error {
color: #f44;
}
.last-updated {
text-align: center;
color: #888;
font-size: 0.9rem;
margin-top: 20px;
}
.timing-player {
background: #223;
border: 1px solid #446;
border-radius: 4px;
padding: 10px;
margin-bottom: 10px;
}
.timing-player .name {
font-weight: bold;
color: #ff8;
}
.timing-player .issue {
color: #f88;
font-size: 0.9rem;
}
.timing-player .stats {
color: #aaa;
font-size: 0.8rem;
margin-top: 5px;
font-family: monospace;
}
.timing-good {
border-color: #464 !important;
background: #232 !important;
}
.timing-warning {
border-color: #664 !important;
background: #332 !important;
}
.timing-critical {
border-color: #644 !important;
background: #322 !important;
}
@media (max-width: 768px) {
.sections {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔍 Player Debug Dashboard</h1>
<p>Real-time tracking of player list changes to debug flapping issues</p>
</div>
<div class="status" id="status">
<div class="loading">Loading player debug data...</div>
</div>
<div class="stats-grid" id="statsGrid" style="display: none;">
<div class="stat-card">
<h3>Current Players</h3>
<div class="stat-value" id="currentPlayers">-</div>
</div>
<div class="stat-card">
<h3>Total Events</h3>
<div class="stat-value" id="totalEvents">-</div>
</div>
<div class="stat-card">
<h3>Flapping Players</h3>
<div class="stat-value" id="flappingCount">-</div>
</div>
<div class="stat-card">
<h3>Recent Activity</h3>
<div class="stat-value" id="recentActivity">-</div>
</div>
<div class="stat-card">
<h3>Timing Issues</h3>
<div class="stat-value" id="timingIssues">-</div>
</div>
<div class="stat-card">
<h3>Tracked Players</h3>
<div class="stat-value" id="trackedPlayers">-</div>
</div>
<div class="stat-card">
<h3>WebSocket Connections</h3>
<div class="stat-value" id="websocketConnections">-</div>
</div>
<div class="stat-card">
<h3>Database Queries</h3>
<div class="stat-value" id="databaseQueries">-</div>
</div>
<div class="stat-card">
<h3>Recent Activity</h3>
<div class="stat-value" id="recentActivityCount">-</div>
</div>
</div>
<div class="sections" id="sections" style="display: none;">
<div class="section">
<h2>🔄 Flapping Players</h2>
<div id="flappingPlayers">
<p style="color: #888; font-style: italic;">No flapping detected</p>
</div>
</div>
<div class="section">
<h2>⏰ Timing Issues</h2>
<div id="timingProblems">
<p style="color: #888; font-style: italic;">Loading timing analysis...</p>
</div>
</div>
<div class="section">
<h2>📊 Player History</h2>
<div id="playerHistory" class="event-log">
<p style="color: #888; font-style: italic;">Loading history...</p>
</div>
</div>
<div class="section">
<h2>📝 Recent Events</h2>
<div id="recentEvents" class="event-log">
<p style="color: #888; font-style: italic;">Loading events...</p>
</div>
</div>
<div class="section">
<h2>🎯 Most Active Players</h2>
<div id="frequentEvents">
<p style="color: #888; font-style: italic;">Loading activity...</p>
</div>
</div>
<div class="section">
<h2>📈 Telemetry Timing</h2>
<div id="telemetryTiming">
<p style="color: #888; font-style: italic;">Loading telemetry data...</p>
</div>
</div>
<div class="section">
<h2>🔌 WebSocket Health</h2>
<div id="websocketHealth">
<p style="color: #888; font-style: italic;">Loading WebSocket data...</p>
</div>
</div>
<div class="section">
<h2>🗄️ Database Performance</h2>
<div id="databasePerformance">
<p style="color: #888; font-style: italic;">Loading database data...</p>
</div>
</div>
<div class="section">
<h2>⚡ Recent Activity</h2>
<div id="recentActivityFeed" class="event-log">
<p style="color: #888; font-style: italic;">Loading recent activity...</p>
</div>
</div>
</div>
<div class="last-updated" id="lastUpdated"></div>
</div>
<script>
let pollInterval;
let isPolling = false;
function formatTimestamp(isoString) {
const date = new Date(isoString);
return date.toLocaleTimeString();
}
function formatDate(isoString) {
const date = new Date(isoString);
return date.toLocaleString();
}
async function fetchDebugData() {
try {
// Fetch all four endpoints
const [flappingResponse, websocketResponse, databaseResponse, activityResponse] = await Promise.all([
fetch('/debug/player-flapping'),
fetch('/debug/websocket-health'),
fetch('/debug/database-performance'),
fetch('/debug/recent-activity')
]);
if (!flappingResponse.ok) {
throw new Error(`HTTP ${flappingResponse.status}: ${flappingResponse.statusText}`);
}
const data = await flappingResponse.json();
const websocketData = websocketResponse.ok ? await websocketResponse.json() : null;
const databaseData = databaseResponse.ok ? await databaseResponse.json() : null;
const activityData = activityResponse.ok ? await activityResponse.json() : null;
// Combine the data
const enhancedData = {
...data,
websocket_health: websocketData,
database_performance: databaseData,
recent_activity: activityData
};
updateUI(enhancedData);
showSuccess();
} catch (error) {
console.error('Failed to fetch debug data:', error);
showError(`Failed to fetch data: ${error.message}`);
}
}
function updateUI(data) {
// Update stats
document.getElementById('currentPlayers').textContent = data.current_players;
document.getElementById('totalEvents').textContent = data.tracking_stats.total_events;
document.getElementById('flappingCount').textContent = data.flapping_analysis.flapping_players.length;
const recentActivity = data.flapping_analysis.recent_activity;
document.getElementById('recentActivity').textContent =
`+${recentActivity.enters} -${recentActivity.exits} (${recentActivity.net_change > 0 ? '+' : ''}${recentActivity.net_change})`;
// Update flapping players
const flappingDiv = document.getElementById('flappingPlayers');
if (data.flapping_analysis.flapping_players.length === 0) {
flappingDiv.innerHTML = '<p style="color: #4f4; font-style: italic;">✅ No flapping detected</p>';
} else {
flappingDiv.innerHTML = data.flapping_analysis.flapping_players.map(player => `
<div class="flapping-player">
<div class="name">${player.character_name}</div>
<div class="score">Flap Score: ${player.flap_score} | Events: ${player.events}</div>
<div style="font-size: 0.8rem; color: #aaa; margin-top: 5px;">
Recent: ${player.recent_activity.join(' → ')}
</div>
</div>
`).join('');
}
// Update history
const historyDiv = document.getElementById('playerHistory');
if (data.history.length === 0) {
historyDiv.innerHTML = '<p style="color: #888; font-style: italic;">No history available</p>';
} else {
historyDiv.innerHTML = data.history.slice().reverse().map(entry => `
<div class="history-entry">
<span class="timestamp">${formatTimestamp(entry.timestamp)}</span>
<span class="player-count">${entry.player_count} players</span>
</div>
`).join('');
}
// Update recent events
const eventsDiv = document.getElementById('recentEvents');
if (data.recent_events.length === 0) {
eventsDiv.innerHTML = '<p style="color: #888; font-style: italic;">No recent events</p>';
} else {
eventsDiv.innerHTML = data.recent_events.slice().reverse().map(event => `
<div class="event-${event.type}">
${formatTimestamp(event.timestamp)} - ${event.character_name} ${event.type === 'enter' ? 'joined' : 'left'} (${event.total_players} total)
</div>
`).join('');
}
// Update frequent events
const frequentDiv = document.getElementById('frequentEvents');
if (data.flapping_analysis.frequent_events.length === 0) {
frequentDiv.innerHTML = '<p style="color: #888; font-style: italic;">No activity data</p>';
} else {
frequentDiv.innerHTML = data.flapping_analysis.frequent_events.map(player => `
<div style="display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #333;">
<span>${player.character_name}</span>
<span style="color: #88f;">${player.event_count} events</span>
</div>
`).join('');
}
// Update timing issues
const timingDiv = document.getElementById('timingProblems');
const timingData = data.timing_analysis || { problem_players: [], summary: { total_tracked_players: 0 } };
document.getElementById('timingIssues').textContent = timingData.problem_players.length;
document.getElementById('trackedPlayers').textContent = timingData.summary.total_tracked_players;
if (timingData.problem_players.length === 0) {
timingDiv.innerHTML = '<p style="color: #4f4; font-style: italic;">✅ No timing issues detected</p>';
} else {
timingDiv.innerHTML = timingData.problem_players.map(player => {
let severityClass = 'timing-good';
const maxInterval = player.max_interval || 0;
const avgInterval = player.avg_interval || 0;
const totalMessages = player.total_messages || 0;
if (maxInterval > 60) severityClass = 'timing-critical';
else if (maxInterval > 45) severityClass = 'timing-warning';
const issue = maxInterval > 30 ?
`Max gap: ${maxInterval.toFixed(1)}s (>${30}s threshold)` :
`Irregular timing patterns detected`;
return `
<div class="timing-player ${severityClass}">
<div class="name">${player.character_name || 'Unknown'}</div>
<div class="issue">${issue}</div>
<div class="stats">
Messages: ${totalMessages} |
Avg gap: ${avgInterval.toFixed(1)}s |
Max gap: ${maxInterval.toFixed(1)}s
</div>
</div>
`;
}).join('');
}
// Update telemetry timing section
const telemetryDiv = document.getElementById('telemetryTiming');
if (timingData.summary.total_tracked_players === 0) {
telemetryDiv.innerHTML = '<p style="color: #888; font-style: italic;">No telemetry data available</p>';
} else {
const healthyCount = timingData.summary.total_tracked_players - timingData.problem_players.length;
telemetryDiv.innerHTML = `
<div style="margin-bottom: 15px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Total Players Tracked:</span>
<span style="color: #88f; font-weight: bold;">${timingData.summary.total_tracked_players}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Healthy Timing:</span>
<span style="color: #4f4; font-weight: bold;">${healthyCount}</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Timing Issues:</span>
<span style="color: #f44; font-weight: bold;">${timingData.problem_players.length}</span>
</div>
</div>
<div style="font-size: 0.9rem; color: #aaa; line-height: 1.4;">
Players with telemetry gaps >30s are flagged as timing issues.
This can cause players to temporarily disappear from the active list.
</div>
`;
}
// Update WebSocket health data
const websocketData = data.websocket_health;
if (websocketData) {
// Update stat card
document.getElementById('websocketConnections').textContent =
`${websocketData.total_connections} (P:${websocketData.plugin_connections}/B:${websocketData.browser_connections})`;
// Update detailed section
const websocketDiv = document.getElementById('websocketHealth');
websocketDiv.innerHTML = `
<div style="margin-bottom: 15px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Plugin Connections:</span>
<span style="color: #88f; font-weight: bold;">${websocketData.plugin_connections}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Browser Connections:</span>
<span style="color: #4f4; font-weight: bold;">${websocketData.browser_connections}</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Total Connections:</span>
<span style="color: #ff8; font-weight: bold;">${websocketData.total_connections}</span>
</div>
</div>
<div style="font-size: 0.9rem; color: #aaa;">
Plugin connections receive telemetry from game clients.<br>
Browser connections show live updates to web users.
</div>
`;
} else {
document.getElementById('websocketConnections').textContent = 'Error';
document.getElementById('websocketHealth').innerHTML = '<p style="color: #f44;">Failed to load WebSocket data</p>';
}
// Update database performance data
const databaseData = data.database_performance;
if (databaseData) {
// Update stat card
document.getElementById('databaseQueries').textContent =
`${databaseData.total_queries} (${databaseData.average_query_time}s avg)`;
// Update detailed section
const databaseDiv = document.getElementById('databasePerformance');
databaseDiv.innerHTML = `
<div style="margin-bottom: 15px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Total Queries:</span>
<span style="color: #88f; font-weight: bold;">${databaseData.total_queries}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Total Query Time:</span>
<span style="color: #4f4; font-weight: bold;">${databaseData.total_query_time}s</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Average Query Time:</span>
<span style="color: #ff8; font-weight: bold;">${databaseData.average_query_time}s</span>
</div>
</div>
<div style="font-size: 0.9rem; color: #aaa;">
Tracks telemetry database write operations performance.<br>
Each telemetry message triggers 1-2 database queries.
</div>
`;
} else {
document.getElementById('databaseQueries').textContent = 'Error';
document.getElementById('databasePerformance').innerHTML = '<p style="color: #f44;">Failed to load database performance data</p>';
}
// Update recent activity data
const activityData = data.recent_activity;
if (activityData) {
// Update stat card
document.getElementById('recentActivityCount').textContent =
`${activityData.total_messages}/${activityData.max_messages}`;
// Update detailed section
const activityDiv = document.getElementById('recentActivityFeed');
if (activityData.recent_messages.length === 0) {
activityDiv.innerHTML = '<p style="color: #888; font-style: italic;">No recent activity</p>';
} else {
activityDiv.innerHTML = activityData.recent_messages.slice().reverse().map(msg => {
const timeStr = formatTimestamp(msg.timestamp);
const killInfo = msg.kill_delta > 0 ? ` (+${msg.kill_delta} kills)` : '';
const queryTime = msg.query_time ? ` ${msg.query_time}ms` : '';
return `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 4px 0; border-bottom: 1px solid #333; font-family: monospace; font-size: 0.85rem;">
<span>
<span style="color: #888;">${timeStr}</span>
<span style="color: #88f; margin-left: 8px;">${msg.character_name}</span>
<span style="color: #aaa; margin-left: 8px;">kills:${msg.kills}${killInfo}</span>
</span>
<span style="color: #666;">${queryTime}</span>
</div>
`;
}).join('');
}
} else {
document.getElementById('recentActivityCount').textContent = 'Error';
document.getElementById('recentActivityFeed').innerHTML = '<p style="color: #f44;">Failed to load recent activity data</p>';
}
// Update last updated
document.getElementById('lastUpdated').textContent = `Last updated: ${formatDate(new Date().toISOString())}`;
}
function showSuccess() {
document.getElementById('status').style.display = 'none';
document.getElementById('statsGrid').style.display = 'grid';
document.getElementById('sections').style.display = 'grid';
}
function showError(message) {
const statusDiv = document.getElementById('status');
statusDiv.innerHTML = `<div class="error">❌ ${message}</div>`;
statusDiv.style.display = 'block';
document.getElementById('statsGrid').style.display = 'none';
document.getElementById('sections').style.display = 'none';
}
function startPolling() {
if (isPolling) return;
isPolling = true;
fetchDebugData(); // Initial fetch
pollInterval = setInterval(fetchDebugData, 5000); // Poll every 5 seconds
}
function stopPolling() {
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
isPolling = false;
}
// Handle page visibility changes
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
stopPolling();
} else {
startPolling();
}
});
// Start polling when page loads
window.addEventListener('load', startPolling);
// Stop polling when page unloads
window.addEventListener('beforeunload', stopPolling);
</script>
</body>
</html>