| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Bot Manager</title> |
| <script src="/socket.io/socket.io.js"></script> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: Arial, sans-serif; |
| background: #1a1a1a; |
| color: #e0e0e0; |
| padding: 10px; |
| } |
| |
| .header { |
| background: #2a2a2a; |
| padding: 15px; |
| margin-bottom: 10px; |
| border: 1px solid #333; |
| } |
| |
| h1 { |
| font-size: 20px; |
| margin-bottom: 10px; |
| color: #fff; |
| } |
| |
| .stats { |
| display: flex; |
| gap: 15px; |
| flex-wrap: wrap; |
| } |
| |
| .stat { |
| background: #333; |
| padding: 8px 12px; |
| border: 1px solid #444; |
| color: #ccc; |
| } |
| |
| .stat strong { |
| color: #fff; |
| } |
| |
| .controls { |
| background: #2a2a2a; |
| padding: 10px; |
| margin-bottom: 10px; |
| border: 1px solid #333; |
| } |
| |
| .btn { |
| background: #4CAF50; |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| cursor: pointer; |
| } |
| |
| .btn:hover { |
| background: #45a049; |
| } |
| |
| .btn:disabled { |
| background: #555; |
| color: #999; |
| cursor: not-allowed; |
| } |
| |
| .bot-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
| gap: 10px; |
| } |
| |
| .bot-card { |
| background: #2a2a2a; |
| border: 1px solid #333; |
| padding: 10px; |
| } |
| |
| .bot-header { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 8px; |
| align-items: center; |
| } |
| |
| .bot-name { |
| font-weight: bold; |
| font-size: 14px; |
| color: #fff; |
| } |
| |
| .status { |
| font-size: 12px; |
| padding: 2px 6px; |
| border: 1px solid; |
| } |
| |
| .status-connected { |
| background: #1b4332; |
| color: #4ade80; |
| border-color: #16a34a; |
| } |
| |
| .status-disconnected, .status-dead { |
| background: #450a0a; |
| color: #f87171; |
| border-color: #dc2626; |
| } |
| |
| .bot-info { |
| font-size: 12px; |
| color: #aaa; |
| margin-bottom: 8px; |
| } |
| |
| .bot-info div { |
| margin: 2px 0; |
| } |
| |
| .info-highlight { |
| color: #4ade80; |
| } |
| |
| .btn-rejoin { |
| background: #2196F3; |
| color: white; |
| border: none; |
| padding: 6px 12px; |
| cursor: pointer; |
| font-size: 12px; |
| width: 100%; |
| } |
| |
| .btn-rejoin:hover { |
| background: #1976D2; |
| } |
| |
| .btn-rejoin:disabled { |
| background: #555; |
| color: #999; |
| cursor: not-allowed; |
| } |
| |
| .timer { |
| font-size: 11px; |
| color: #888; |
| text-align: center; |
| margin-top: 4px; |
| } |
| |
| .loading { |
| padding: 20px; |
| color: #888; |
| text-align: center; |
| } |
| |
| @media (max-width: 600px) { |
| .bot-grid { |
| grid-template-columns: 1fr; |
| } |
| |
| h1 { |
| font-size: 18px; |
| } |
| |
| .stats { |
| font-size: 12px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="header"> |
| <h1>🤖 Bot Manager</h1> |
| <div class="stats"> |
| <div class="stat">Total: <strong id="totalBots">0</strong></div> |
| <div class="stat">Connected: <strong id="connectedBots">0</strong></div> |
| <div class="stat">Disconnected: <strong id="disconnectedBots">0</strong></div> |
| <div class="stat">Deaths: <strong id="totalDeaths">0</strong></div> |
| </div> |
| </div> |
| |
| <div class="controls"> |
| <button class="btn" onclick="refreshSheet()">Refresh Sheet</button> |
| </div> |
| |
| <div id="botContainer" class="bot-grid"> |
| <div class="loading">Loading...</div> |
| </div> |
| |
| <script> |
| const socket = io(); |
| let botsData = []; |
| |
| socket.on('connect', () => { |
| console.log('Connected to server'); |
| }); |
| |
| socket.on('botUpdate', (data) => { |
| botsData = data; |
| updateUI(); |
| }); |
| |
| socket.on('reconnectResult', (result) => { |
| if (!result.success) { |
| alert(`Cannot reconnect ${result.botName} yet. Wait 1 hour between reconnects.`); |
| } |
| }); |
| |
| function updateUI() { |
| const container = document.getElementById('botContainer'); |
| |
| |
| const validBots = botsData.filter(bot => bot.inSheet); |
| |
| if (validBots.length === 0) { |
| container.innerHTML = '<div class="loading">No bots configured in sheet.</div>'; |
| updateStats(0, 0, 0, 0); |
| return; |
| } |
| |
| let html = ''; |
| let totalBots = 0; |
| let connectedBots = 0; |
| let disconnectedBots = 0; |
| let totalDeaths = 0; |
| |
| validBots.forEach(bot => { |
| totalBots++; |
| if (bot.status === 'Connected') { |
| connectedBots++; |
| } else { |
| disconnectedBots++; |
| } |
| totalDeaths += bot.deathCount; |
| |
| const statusClass = bot.status === 'Connected' ? 'status-connected' : |
| bot.status === 'Connecting...' ? 'status-connected' : |
| 'status-disconnected'; |
| |
| const showRejoinButton = (bot.status === 'Disconnected' || bot.status === 'Dead' || |
| bot.status === 'Connection Failed' || bot.status === 'Server already has a bot') && |
| bot.inSheet; |
| const canRejoinNow = bot.canReconnect; |
| const timeRemaining = bot.timeUntilReconnect; |
| |
| html += ` |
| <div class="bot-card"> |
| <div class="bot-header"> |
| <div class="bot-name">${bot.botName}</div> |
| <div class="status ${statusClass}">${bot.status}</div> |
| </div> |
| <div class="bot-info"> |
| <div>Deaths: ${bot.deathCount}</div> |
| ${bot.status === 'Connected' && bot.connectedDuration > 0 ? |
| `<div class="info-highlight">Connected: ${formatUptime(bot.connectedDuration)}</div>` : ''} |
| </div> |
| ${showRejoinButton ? ` |
| <button class="btn-rejoin" |
| onclick="reconnectBot('${bot.botName}')" |
| ${!canRejoinNow ? 'disabled' : ''}> |
| ${canRejoinNow ? 'Rejoin' : 'Wait to Rejoin'} |
| </button> |
| ${!canRejoinNow && timeRemaining > 0 ? |
| `<div class="timer">Can rejoin in: ${formatUptime(timeRemaining)}</div>` : ''} |
| ` : ''} |
| </div> |
| `; |
| }); |
| |
| container.innerHTML = html; |
| updateStats(totalBots, connectedBots, disconnectedBots, totalDeaths); |
| } |
| |
| function updateStats(total, connected, disconnected, deaths) { |
| document.getElementById('totalBots').textContent = total; |
| document.getElementById('connectedBots').textContent = connected; |
| document.getElementById('disconnectedBots').textContent = disconnected; |
| document.getElementById('totalDeaths').textContent = deaths; |
| } |
| |
| function formatUptime(seconds) { |
| if (seconds === 0) return '0s'; |
| const days = Math.floor(seconds / 86400); |
| const hours = Math.floor((seconds % 86400) / 3600); |
| const minutes = Math.floor((seconds % 3600) / 60); |
| const secs = seconds % 60; |
| |
| if (days > 0) { |
| return `${days}d ${hours}h ${minutes}m`; |
| } else if (hours > 0) { |
| return `${hours}h ${minutes}m`; |
| } else if (minutes > 0) { |
| return `${minutes}m ${secs}s`; |
| } else { |
| return `${secs}s`; |
| } |
| } |
| |
| function reconnectBot(botName) { |
| socket.emit('reconnectBot', botName); |
| } |
| |
| function refreshSheet() { |
| socket.emit('refreshSheet'); |
| } |
| |
| |
| setInterval(() => { |
| if (botsData.length > 0) { |
| botsData.forEach(bot => { |
| if (bot.timeUntilReconnect > 0) { |
| bot.timeUntilReconnect = Math.max(0, bot.timeUntilReconnect - 2); |
| } |
| if (bot.status === 'Connected' && bot.connectedDuration >= 0) { |
| bot.connectedDuration += 2; |
| } |
| bot.canReconnect = bot.timeUntilReconnect === 0 && |
| (bot.status === 'Disconnected' || bot.status === 'Dead' || |
| bot.status === 'Connection Failed' || bot.status === 'Server already has a bot') && |
| bot.inSheet; |
| }); |
| updateUI(); |
| } |
| }, 2000); |
| </script> |
| </body> |
| </html> |