|
|
<!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> |