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 %}