admin order line
This commit is contained in:
8
db.py
8
db.py
@@ -25,6 +25,7 @@ def init_db():
|
|||||||
payload TEXT NOT NULL, -- JSON string
|
payload TEXT NOT NULL, -- JSON string
|
||||||
status TEXT NOT NULL DEFAULT 'pending', -- pending | executing | done | failed
|
status TEXT NOT NULL DEFAULT 'pending', -- pending | executing | done | failed
|
||||||
result_msg TEXT,
|
result_msg TEXT,
|
||||||
|
position INTEGER, -- manual sort order for build queue (lower = first)
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
)
|
)
|
||||||
@@ -75,6 +76,7 @@ def init_db():
|
|||||||
'ALTER TABLE town_state ADD COLUMN y REAL',
|
'ALTER TABLE town_state ADD COLUMN y REAL',
|
||||||
'ALTER TABLE town_state ADD COLUMN sea INTEGER',
|
'ALTER TABLE town_state ADD COLUMN sea INTEGER',
|
||||||
'ALTER TABLE commands ADD COLUMN player_id TEXT',
|
'ALTER TABLE commands ADD COLUMN player_id TEXT',
|
||||||
|
'ALTER TABLE commands ADD COLUMN position INTEGER',
|
||||||
'ALTER TABLE farm_settings ADD COLUMN bandit_camp_enabled INTEGER NOT NULL DEFAULT 0',
|
'ALTER TABLE farm_settings ADD COLUMN bandit_camp_enabled INTEGER NOT NULL DEFAULT 0',
|
||||||
"ALTER TABLE clan_members ADD COLUMN features TEXT NOT NULL DEFAULT 'farm,admin'",
|
"ALTER TABLE clan_members ADD COLUMN features TEXT NOT NULL DEFAULT 'farm,admin'",
|
||||||
'ALTER TABLE users ADD COLUMN clan_id INTEGER REFERENCES clans(id)',
|
'ALTER TABLE users ADD COLUMN clan_id INTEGER REFERENCES clans(id)',
|
||||||
@@ -84,6 +86,12 @@ def init_db():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass # column already exists
|
pass # column already exists
|
||||||
|
|
||||||
|
# Back-fill position for existing rows that have NULL position
|
||||||
|
try:
|
||||||
|
c.execute('UPDATE commands SET position = id WHERE position IS NULL')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Users — website admin accounts
|
# Users — website admin accounts
|
||||||
c.execute('''
|
c.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ def _fetch_pending_builds_all_towns(c, player_id):
|
|||||||
SELECT * FROM commands
|
SELECT * FROM commands
|
||||||
WHERE status = 'pending' AND type = 'build'
|
WHERE status = 'pending' AND type = 'build'
|
||||||
AND player_id = ? AND town_id = ?
|
AND player_id = ? AND town_id = ?
|
||||||
ORDER BY updated_at ASC, id ASC
|
ORDER BY position ASC, id ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
''', (player_id, town_id)).fetchone()
|
''', (player_id, town_id)).fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
|
|||||||
@@ -269,6 +269,54 @@ def captcha_status():
|
|||||||
return jsonify({'captcha_active': active})
|
return jsonify({'captcha_active': active})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# GET /dashboard/commands/queue
|
||||||
|
# Returns pending+executing BUILD commands for a specific town,
|
||||||
|
# ordered by their manual position (for the per-town build queue UI).
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/dashboard/commands/queue', methods=['GET'])
|
||||||
|
def get_town_build_queue():
|
||||||
|
player_id = request.args.get('player_id')
|
||||||
|
town_id = request.args.get('town_id')
|
||||||
|
conn = get_db()
|
||||||
|
rows = conn.execute('''
|
||||||
|
SELECT id, town_id, town_name, type, payload, status, result_msg, position, created_at, updated_at
|
||||||
|
FROM commands
|
||||||
|
WHERE player_id = ? AND town_id = ? AND type = 'build'
|
||||||
|
AND status IN ('pending', 'executing')
|
||||||
|
ORDER BY position ASC, id ASC
|
||||||
|
''', (player_id, town_id)).fetchall()
|
||||||
|
conn.close()
|
||||||
|
return jsonify([dict(r) for r in rows])
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# POST /dashboard/commands/reorder
|
||||||
|
# Accepts { player_id, town_id, order: [id1, id2, ...] }
|
||||||
|
# and updates position for each command in the list.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/dashboard/commands/reorder', methods=['POST'])
|
||||||
|
def reorder_commands():
|
||||||
|
data = request.get_json(silent=True) or {}
|
||||||
|
player_id = data.get('player_id')
|
||||||
|
town_id = data.get('town_id')
|
||||||
|
order = data.get('order', []) # list of command ids in desired order
|
||||||
|
|
||||||
|
if not player_id or not town_id or not order:
|
||||||
|
return jsonify({'error': 'missing player_id, town_id or order'}), 400
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
for idx, cmd_id in enumerate(order):
|
||||||
|
conn.execute('''
|
||||||
|
UPDATE commands
|
||||||
|
SET position = ?
|
||||||
|
WHERE id = ? AND player_id = ? AND town_id = ?
|
||||||
|
''', (idx + 1, cmd_id, player_id, str(town_id)))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'ok': True})
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# GET /dashboard/commands
|
# GET /dashboard/commands
|
||||||
# Returns command history (last 50) for the log panel.
|
# Returns command history (last 50) for the log panel.
|
||||||
@@ -326,14 +374,26 @@ def create_command():
|
|||||||
return jsonify({'error': 'client_offline', 'message': 'Το script είναι offline — δεν μπορείτε να στείλετε εντολές.'}), 503
|
return jsonify({'error': 'client_offline', 'message': 'Το script είναι offline — δεν μπορείτε να στείλετε εντολές.'}), 503
|
||||||
|
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
|
# Assign position = one more than the current max for this town's pending build queue
|
||||||
|
if cmd_type == 'build':
|
||||||
|
pos_row = c.execute('''
|
||||||
|
SELECT MAX(position) as max_pos FROM commands
|
||||||
|
WHERE player_id = ? AND town_id = ? AND type = 'build' AND status IN (''pending'', ''executing'')
|
||||||
|
''', (str(data['player_id']), str(data['town_id']))).fetchone()
|
||||||
|
position = (pos_row['max_pos'] or 0) + 1
|
||||||
|
else:
|
||||||
|
position = None
|
||||||
|
|
||||||
c.execute('''
|
c.execute('''
|
||||||
INSERT INTO commands (town_id, town_name, type, payload, status, created_at, updated_at, player_id)
|
INSERT INTO commands (town_id, town_name, type, payload, status, position, created_at, updated_at, player_id)
|
||||||
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)
|
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?)
|
||||||
''', (
|
''', (
|
||||||
str(data['town_id']),
|
str(data['town_id']),
|
||||||
data.get('town_name', ''),
|
data.get('town_name', ''),
|
||||||
cmd_type,
|
cmd_type,
|
||||||
json.dumps(data['payload']),
|
json.dumps(data['payload']),
|
||||||
|
position,
|
||||||
datetime.utcnow().isoformat(),
|
datetime.utcnow().isoformat(),
|
||||||
datetime.utcnow().isoformat(),
|
datetime.utcnow().isoformat(),
|
||||||
str(data['player_id'])
|
str(data['player_id'])
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ window.fetchTowns = async function() {
|
|||||||
window.renderBuildingDropdown();
|
window.renderBuildingDropdown();
|
||||||
window.renderUnitDropdown();
|
window.renderUnitDropdown();
|
||||||
window.renderTownDetails();
|
window.renderTownDetails();
|
||||||
|
// Refresh the build queue panel if in queue mode
|
||||||
|
if (window._logPanelMode === 'queue') {
|
||||||
|
window.fetchBuildQueue(window.selectedTownId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.updateServerStatus(false);
|
window.updateServerStatus(false);
|
||||||
@@ -51,7 +55,9 @@ window.fetchLog = async function() {
|
|||||||
const res = await fetch('/dashboard/commands?player_id=' + window.PLAYER_ID);
|
const res = await fetch('/dashboard/commands?player_id=' + window.PLAYER_ID);
|
||||||
const cmds = await res.json();
|
const cmds = await res.json();
|
||||||
window.cmds = cmds; // Save globally so viewer can see reserved resources
|
window.cmds = cmds; // Save globally so viewer can see reserved resources
|
||||||
|
if (window._logPanelMode === 'log') {
|
||||||
window.renderLog(cmds);
|
window.renderLog(cmds);
|
||||||
|
}
|
||||||
if (window.selectedTownId) window.renderTownDetails();
|
if (window.selectedTownId) window.renderTownDetails();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
@@ -192,7 +198,12 @@ window.sendCommand = async function() {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
|
// Refresh whichever panel is active
|
||||||
|
if (type === 'build' && window._logPanelMode === 'queue') {
|
||||||
|
window.fetchBuildQueue(town.town_id);
|
||||||
|
} else {
|
||||||
window.fetchLog();
|
window.fetchLog();
|
||||||
|
}
|
||||||
} else if (data.error === 'client_offline') {
|
} else if (data.error === 'client_offline') {
|
||||||
alert(data.message || 'Το script είναι offline.');
|
alert(data.message || 'Το script είναι offline.');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,11 +4,15 @@
|
|||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
window.fetchTowns();
|
window.fetchTowns();
|
||||||
window.fetchLog();
|
window.fetchLog(); // pre-loads cmds globally even in queue mode
|
||||||
window.fetchClientStatus();
|
window.fetchClientStatus();
|
||||||
window.fetchCaptchaStatus();
|
window.fetchCaptchaStatus();
|
||||||
setInterval(window.fetchTowns, window.POLL_INTERVAL);
|
setInterval(window.fetchTowns, window.POLL_INTERVAL);
|
||||||
setInterval(window.fetchLog, window.POLL_INTERVAL);
|
// In log mode: fetchLog refreshes the panel. In queue mode: refreshLogPanel polls the queue.
|
||||||
|
setInterval(() => {
|
||||||
|
window.fetchLog(); // always keep cmds cache fresh for resource display
|
||||||
|
window.refreshLogPanel(); // refresh whichever panel is visible
|
||||||
|
}, window.POLL_INTERVAL);
|
||||||
setInterval(window.fetchClientStatus, window.POLL_INTERVAL);
|
setInterval(window.fetchClientStatus, window.POLL_INTERVAL);
|
||||||
setInterval(window.fetchCaptchaStatus, 5000); // check every 5s
|
setInterval(window.fetchCaptchaStatus, 5000); // check every 5s
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,171 @@
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
// Command Log Component
|
// Command Log & Build Queue Component
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|
||||||
|
// -- Panel state: 'queue' | 'log' ----------------------------
|
||||||
|
window._logPanelMode = 'queue';
|
||||||
|
|
||||||
|
// ---- Toggle buttons -------------------------------------------
|
||||||
|
window.switchToQueueMode = function() {
|
||||||
|
window._logPanelMode = 'queue';
|
||||||
|
document.getElementById('tab-queue').classList.add('tab-active');
|
||||||
|
document.getElementById('tab-log').classList.remove('tab-active');
|
||||||
|
window.refreshLogPanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.switchToLogMode = function() {
|
||||||
|
window._logPanelMode = 'log';
|
||||||
|
document.getElementById('tab-log').classList.add('tab-active');
|
||||||
|
document.getElementById('tab-queue').classList.remove('tab-active');
|
||||||
|
window.fetchLog();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Main dispatcher ------------------------------------------
|
||||||
|
window.refreshLogPanel = function() {
|
||||||
|
if (window._logPanelMode === 'queue') {
|
||||||
|
const town = window.getSelectedTown();
|
||||||
|
if (town) {
|
||||||
|
window.fetchBuildQueue(town.town_id);
|
||||||
|
} else {
|
||||||
|
document.getElementById('log-content').innerHTML =
|
||||||
|
'<p style="color:#555;font-size:0.85rem;padding:12px 0;">← Επιλέξτε πόλη για να δείτε την ουρά.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// BUILD QUEUE (per-town, draggable)
|
||||||
|
// ================================================================
|
||||||
|
window.fetchBuildQueue = async function(townId) {
|
||||||
|
if (window._logPanelMode !== 'queue') return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/dashboard/commands/queue?player_id=${window.PLAYER_ID}&town_id=${encodeURIComponent(townId)}`);
|
||||||
|
const cmds = await res.json();
|
||||||
|
window.renderBuildQueue(cmds, townId);
|
||||||
|
} catch(e) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drag state
|
||||||
|
let _dragSrcIdx = null;
|
||||||
|
|
||||||
|
window.renderBuildQueue = function(cmds, townId) {
|
||||||
|
const el = document.getElementById('log-content');
|
||||||
|
|
||||||
|
if (!cmds || cmds.length === 0) {
|
||||||
|
el.innerHTML = `
|
||||||
|
<div style="text-align:center;padding:2rem 1rem;color:#444;">
|
||||||
|
<div style="font-size:2rem;margin-bottom:0.5rem;">🏗️</div>
|
||||||
|
<p style="font-size:0.85rem;">Η ουρά κατασκευών είναι κενή.</p>
|
||||||
|
<p style="font-size:0.75rem;color:#333;margin-top:0.3rem;">Χρησιμοποιήστε την φόρμα για να προσθέσετε κατασκευές.</p>
|
||||||
|
</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = cmds.map((cmd, idx) => {
|
||||||
|
const p = typeof cmd.payload === 'string' ? JSON.parse(cmd.payload) : cmd.payload;
|
||||||
|
const nameGr = window.BUILDING_NAMES_GR?.[p.building_id] || p.building_id || '?';
|
||||||
|
const icon = window.BUILDING_ICONS?.[p.building_id] || '🏗️';
|
||||||
|
const isExec = cmd.status === 'executing';
|
||||||
|
|
||||||
|
const statusDot = isExec
|
||||||
|
? `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#4acc64;box-shadow:0 0 5px #4acc64;flex-shrink:0;" title="Εκτελείται"></span>`
|
||||||
|
: `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#555;flex-shrink:0;" title="Σε αναμονή"></span>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="bq-row" draggable="true"
|
||||||
|
data-idx="${idx}" data-id="${cmd.id}" data-town="${townId}"
|
||||||
|
ondragstart="window._bqDragStart(event,${idx})"
|
||||||
|
ondragover="window._bqDragOver(event)"
|
||||||
|
ondrop="window._bqDrop(event,${idx},'${townId}')"
|
||||||
|
ondragend="window._bqDragEnd(event)">
|
||||||
|
<span class="bq-handle" title="Σύρε για αναδιάταξη">⠿</span>
|
||||||
|
<span class="bq-pos">${idx + 1}</span>
|
||||||
|
${statusDot}
|
||||||
|
<span class="bq-icon">${icon}</span>
|
||||||
|
<span class="bq-name">${nameGr}</span>
|
||||||
|
<button class="bq-cancel-btn" onclick="window._bqCancel(${cmd.id})" title="Ακύρωση">✕</button>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
el.innerHTML = `<div id="bq-list">${rows}</div>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Drag-and-drop handlers -----------------------------------
|
||||||
|
window._bqDragStart = function(e, idx) {
|
||||||
|
_dragSrcIdx = idx;
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
setTimeout(() => {
|
||||||
|
const rows = document.querySelectorAll('.bq-row');
|
||||||
|
if (rows[idx]) rows[idx].style.opacity = '0.4';
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
window._bqDragOver = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
// Highlight target row
|
||||||
|
document.querySelectorAll('.bq-row').forEach(r => r.classList.remove('bq-drag-over'));
|
||||||
|
const row = e.currentTarget;
|
||||||
|
if (row) row.classList.add('bq-drag-over');
|
||||||
|
};
|
||||||
|
|
||||||
|
window._bqDrop = function(e, targetIdx, townId) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (_dragSrcIdx === null || _dragSrcIdx === targetIdx) return;
|
||||||
|
|
||||||
|
// Re-order the DOM
|
||||||
|
const list = document.getElementById('bq-list');
|
||||||
|
const rows = Array.from(list.querySelectorAll('.bq-row'));
|
||||||
|
const movedRow = rows.splice(_dragSrcIdx, 1)[0];
|
||||||
|
rows.splice(targetIdx, 0, movedRow);
|
||||||
|
|
||||||
|
// Update numbering & opacity
|
||||||
|
rows.forEach((r, i) => {
|
||||||
|
r.style.opacity = '1';
|
||||||
|
r.classList.remove('bq-drag-over');
|
||||||
|
r.dataset.idx = i;
|
||||||
|
r.querySelector('.bq-pos').textContent = i + 1;
|
||||||
|
r.setAttribute('ondragstart', `window._bqDragStart(event,${i})`);
|
||||||
|
r.setAttribute('ondrop', `window._bqDrop(event,${i},'${townId}')`);
|
||||||
|
});
|
||||||
|
list.innerHTML = '';
|
||||||
|
rows.forEach(r => list.appendChild(r));
|
||||||
|
|
||||||
|
// Persist new order to server
|
||||||
|
const orderedIds = rows.map(r => parseInt(r.dataset.id));
|
||||||
|
fetch('/dashboard/commands/reorder', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ player_id: window.PLAYER_ID, town_id: townId, order: orderedIds })
|
||||||
|
});
|
||||||
|
|
||||||
|
_dragSrcIdx = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
window._bqDragEnd = function(e) {
|
||||||
|
document.querySelectorAll('.bq-row').forEach(r => {
|
||||||
|
r.style.opacity = '1';
|
||||||
|
r.classList.remove('bq-drag-over');
|
||||||
|
});
|
||||||
|
_dragSrcIdx = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
window._bqCancel = async function(id) {
|
||||||
|
await fetch(`/dashboard/commands/${id}`, { method: 'DELETE' });
|
||||||
|
// Refresh the queue for the currently selected town
|
||||||
|
const town = window.getSelectedTown();
|
||||||
|
if (town) window.fetchBuildQueue(town.town_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// COMMAND LOG (full history, existing behaviour)
|
||||||
|
// ================================================================
|
||||||
window.renderLog = function(cmds) {
|
window.renderLog = function(cmds) {
|
||||||
|
if (window._logPanelMode !== 'log') return;
|
||||||
const el = document.getElementById('log-content');
|
const el = document.getElementById('log-content');
|
||||||
if (!cmds.length) {
|
if (!cmds.length) {
|
||||||
el.innerHTML = '<p id="empty-log">No commands sent yet.</p>';
|
el.innerHTML = '<p id="empty-log" style="color:#555;font-size:0.85rem;padding:12px 0;">No commands sent yet.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,10 +180,11 @@ window.renderLog = function(cmds) {
|
|||||||
desc = `Recruit: ${p.amount}x ${nameGr}`;
|
desc = `Recruit: ${p.amount}x ${nameGr}`;
|
||||||
} else if (cmd.type === 'market_offer') {
|
} else if (cmd.type === 'market_offer') {
|
||||||
desc = `Market: ${p.offer} ${p.offer_type} ➞ ${p.demand} ${p.demand_type}`;
|
desc = `Market: ${p.offer} ${p.offer_type} ➞ ${p.demand} ${p.demand_type}`;
|
||||||
|
} else {
|
||||||
|
desc = cmd.type;
|
||||||
}
|
}
|
||||||
const statusClass = `status-${cmd.status}`;
|
const statusClass = `status-${cmd.status}`;
|
||||||
const cancelBtn = `<button class="btn btn-danger btn-sm" onclick="cancelCommand(${cmd.id})">✕</button>`;
|
const cancelBtn = `<button class="btn btn-danger btn-sm" onclick="cancelCommand(${cmd.id})">✕</button>`;
|
||||||
|
|
||||||
const timeStr = new Date(cmd.created_at + 'Z').toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
const timeStr = new Date(cmd.created_at + 'Z').toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
||||||
|
|
||||||
return `<tr>
|
return `<tr>
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ window.selectTown = function(id) {
|
|||||||
window.renderBuildingDropdown();
|
window.renderBuildingDropdown();
|
||||||
window.renderUnitDropdown();
|
window.renderUnitDropdown();
|
||||||
window.renderTownDetails();
|
window.renderTownDetails();
|
||||||
|
// Refresh build queue panel for the newly selected town
|
||||||
|
if (window._logPanelMode === 'queue') {
|
||||||
|
window.fetchBuildQueue(id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.getSelectedTown = function() {
|
window.getSelectedTown = function() {
|
||||||
|
|||||||
@@ -219,11 +219,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom right: Command log -->
|
<!-- Bottom right: Build Queue / Command Log (tabbed) -->
|
||||||
<div id="log-panel">
|
<div id="log-panel">
|
||||||
<h2>Command Log</h2>
|
<div style="display:flex; align-items:center; gap:8px; margin-bottom:12px; border-bottom:1px solid #2a3a5a; padding-bottom:10px;">
|
||||||
|
<h2 style="margin:0; flex:1;">Ουρά Κατασκευών</h2>
|
||||||
|
<button id="tab-queue" class="log-tab-btn tab-active" onclick="window.switchToQueueMode()">🏗️ Ουρά</button>
|
||||||
|
<button id="tab-log" class="log-tab-btn" onclick="window.switchToLogMode()">📋 Ιστορικό</button>
|
||||||
|
</div>
|
||||||
<div id="log-content">
|
<div id="log-content">
|
||||||
<p id="empty-log">No commands sent yet.</p>
|
<p style="color:#555;font-size:0.85rem;padding:12px 0;">← Επιλέξτε πόλη για να δείτε την ουρά.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -254,14 +258,78 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Tab buttons for queue / log toggle */
|
||||||
|
.log-tab-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #2a3a5a;
|
||||||
|
color: #666;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.log-tab-btn:hover { border-color: #4a7aaa; color: #aaa; }
|
||||||
|
.log-tab-btn.tab-active { border-color: #c8a44a; color: #c8a44a; background: rgba(200,164,74,0.1); }
|
||||||
|
|
||||||
|
/* Draggable build queue row */
|
||||||
|
.bq-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #1a2a3a;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
background: #0d1e30;
|
||||||
|
cursor: default;
|
||||||
|
transition: background 0.15s, border-color 0.15s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.bq-row:hover { background: #112038; border-color: #2a4a6a; }
|
||||||
|
.bq-row.bq-drag-over { border-color: #c8a44a; background: rgba(200,164,74,0.08); }
|
||||||
|
.bq-handle {
|
||||||
|
cursor: grab;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #3a5a7a;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
.bq-handle:hover { color: #c8a44a; }
|
||||||
|
.bq-pos {
|
||||||
|
width: 18px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: #3a5a7a;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.bq-icon { font-size: 1.1rem; flex-shrink: 0; }
|
||||||
|
.bq-name { flex: 1; font-size: 0.88rem; color: #d0d0d0; }
|
||||||
|
.bq-cancel-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #3a2a2a;
|
||||||
|
color: #884444;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.bq-cancel-btn:hover { background: rgba(200,80,80,0.15); border-color: #cc4444; color: #ff6666; }
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
window.PLAYER_ID = "{{ player_id }}";
|
window.PLAYER_ID = "{{ player_id }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/js/state.js?v=5"></script>
|
<script src="/static/js/state.js?v=6"></script>
|
||||||
<script src="/static/js/components/townViewer.js?v=5"></script>
|
<script src="/static/js/components/townViewer.js?v=6"></script>
|
||||||
<script src="/static/js/components/commandForm.js?v=5"></script>
|
<script src="/static/js/components/commandForm.js?v=6"></script>
|
||||||
<script src="/static/js/components/commandLog.js?v=5"></script>
|
<script src="/static/js/components/commandLog.js?v=6"></script>
|
||||||
<script src="/static/js/api.js?v=5"></script>
|
<script src="/static/js/api.js?v=6"></script>
|
||||||
<script src="/static/js/app.js?v=5"></script>
|
<script src="/static/js/app.js?v=6"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -243,7 +243,7 @@
|
|||||||
<div class="status-bar" id="status-bar"></div>
|
<div class="status-bar" id="status-bar"></div>
|
||||||
|
|
||||||
<!-- Warehouse-full notice (hidden by default) -->
|
<!-- Warehouse-full notice (hidden by default) -->
|
||||||
<div id="warehouse-full-banner" style="display:none; background: linear-gradient(90deg, #5a1a00, #8b2500); border: 1px solid #ff6600; border-radius: 8px; padding: 12px 18px; margin-bottom: 1rem; display: flex; align-items: center; gap: 12px; font-weight: 600;">
|
<div id="warehouse-full-banner" style="display:none; background: linear-gradient(90deg, #5a1a00, #8b2500); border: 1px solid #ff6600; border-radius: 8px; padding: 12px 18px; margin-bottom: 1rem; align-items: center; gap: 12px; font-weight: 600;">
|
||||||
<span style="font-size: 1.4rem;">📦</span>
|
<span style="font-size: 1.4rem;">📦</span>
|
||||||
<span>
|
<span>
|
||||||
<strong style="color:#ff9933;">Αποθήκη Γεμάτη!</strong>
|
<strong style="color:#ff9933;">Αποθήκη Γεμάτη!</strong>
|
||||||
@@ -530,7 +530,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Warehouse full notice --
|
|
||||||
async function checkWarehouseStatus() {
|
async function checkWarehouseStatus() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/farm_status?player_id=${PLAYER_ID}`);
|
const res = await fetch(`/api/farm_status?player_id=${PLAYER_ID}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user