diff --git a/routes/dashboard.py b/routes/dashboard.py index a94d28c..1a73f02 100644 --- a/routes/dashboard.py +++ b/routes/dashboard.py @@ -14,12 +14,16 @@ dashboard = Blueprint('dashboard', __name__) def index(): conn = get_db() rows = conn.execute(''' - SELECT player, player_id, MAX(updated_at) as last_seen + SELECT player, player_id, MAX(updated_at) as last_seen, MAX(world_id) as world_id FROM town_state WHERE player IS NOT NULL GROUP BY player, player_id ORDER BY player ASC ''').fetchall() + + # Pre-fetch all active captchas + captcha_rows = conn.execute("SELECT key, value FROM kv_store WHERE key LIKE 'captcha_active_%'").fetchall() + active_captchas = {r['key'].replace('captcha_active_', ''): True for r in captcha_rows if r['value'] == '1'} conn.close() players = [] @@ -37,7 +41,9 @@ def index(): players.append({ 'player': r['player'], 'player_id': r['player_id'], - 'is_online': is_online + 'world_id': r['world_id'] or 'Unknown', + 'is_online': is_online, + 'captcha_active': active_captchas.get(r['player_id'], False) }) return render_template('index.html', players=players) diff --git a/static/js/api.js b/static/js/api.js index 659c813..58cab4b 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -8,6 +8,11 @@ window.fetchTowns = async function() { window.towns = await res.json(); window.renderTowns(); window.updateServerStatus(true); + + // Automatically select the first town if none is selected + if (!window.selectedTownId && window.towns && window.towns.length > 0) { + window.selectTown(window.towns[0].town_id); + } if (window.selectedTownId) { window.renderBuildQueuePreview(); @@ -45,7 +50,9 @@ window.fetchLog = async function() { try { const res = await fetch('/dashboard/commands?player_id=' + window.PLAYER_ID); const cmds = await res.json(); + window.cmds = cmds; // Save globally so viewer can see reserved resources window.renderLog(cmds); + if (window.selectedTownId) window.renderTownDetails(); } catch (e) {} }; diff --git a/static/js/components/commandLog.js b/static/js/components/commandLog.js index 18a5ae9..83ce05f 100644 --- a/static/js/components/commandLog.js +++ b/static/js/components/commandLog.js @@ -22,8 +22,10 @@ window.renderLog = function(cmds) { const statusClass = `status-${cmd.status}`; const cancelBtn = ``; + const timeStr = new Date(cmd.created_at + 'Z').toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); + return ` - #${cmd.id} + #${cmd.id}
${timeStr} ${cmd.town_name || cmd.town_id} ${desc} ${cmd.status} diff --git a/static/js/components/townViewer.js b/static/js/components/townViewer.js index 607c53f..480fff3 100644 --- a/static/js/components/townViewer.js +++ b/static/js/components/townViewer.js @@ -111,13 +111,48 @@ window.renderTownDetails = function() { return 'color: #fff;'; }; + // Calculate resources reserved in the bot's pending queues + let reservedWood = 0, reservedStone = 0, reservedIron = 0; + if (window.cmds) { + window.cmds.forEach(cmd => { + if ((cmd.status === 'pending' || cmd.status === 'executing') && cmd.town_id == t.town_id) { + if (cmd.type === 'build' && t.build_data) { + const p = typeof cmd.payload === 'string' ? JSON.parse(cmd.payload) : cmd.payload; + const cost = t.build_data[p.building_id]; + if (cost) { + reservedWood += cost.wood || 0; + reservedStone += cost.stone || 0; + reservedIron += cost.iron || 0; + } + } else if (cmd.type === 'recruit' && t.unit_data) { + const p = typeof cmd.payload === 'string' ? JSON.parse(cmd.payload) : cmd.payload; + const amt = parseInt(p.amount) || 1; + const cost = t.unit_data[p.unit_id]; + if (cost) { + reservedWood += (cost.wood || 0) * amt; + reservedStone += (cost.stone || 0) * amt; + reservedIron += (cost.iron || 0) * amt; + } + } + } + }); + } + + const resHtml = (actual, reserved) => { + let base = `${window.fmt(actual)}`; + if (reserved > 0) { + base += ` (-${window.fmt(reserved)})`; + } + return base; + }; + const capFmt = (t.resources.storage != null && t.resources.storage !== '') ? window.fmt(t.resources.storage) : '?'; document.getElementById('td-resources').innerHTML = `
Χωρητικότητα: ${capFmt}
-
${window.RES_ICONS.wood} Ξύλο: ${window.fmt(t.resources.wood)}
-
${window.RES_ICONS.stone} Πέτρα: ${window.fmt(t.resources.stone)}
-
${window.RES_ICONS.iron} Ασήμι: ${window.fmt(t.resources.iron)}
+
${window.RES_ICONS.wood} Ξύλο: ${resHtml(t.resources.wood, reservedWood)}
+
${window.RES_ICONS.stone} Πέτρα: ${resHtml(t.resources.stone, reservedStone)}
+
${window.RES_ICONS.iron} Ασήμι: ${resHtml(t.resources.iron, reservedIron)}
${window.RES_ICONS.pop} Πληθυσμός: ${t.resources.population}
`; diff --git a/templates/index.html b/templates/index.html index 1059e23..c2c63e4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -69,19 +69,27 @@
- {{ p.player }} (ID: {{ p.player_id }}) + {{ p.player }} [{{ p.world_id }}] (ID: {{ p.player_id }}) +
+
+ {% if p.captcha_active %} + + ⚠️ Captcha + + {% endif %} + + {% if p.is_online %} + + + Online + + {% else %} + + + Offline + + {% endif %}
- {% if p.is_online %} - - - Online - - {% else %} - - - Offline - - {% endif %}
{% endfor %}