Files
grepo-remote/templates/farm.html
2026-05-02 01:40:21 +03:00

733 lines
27 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="el">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Farm Manager — Grepolis Remote</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0f0f1a;
font-family: 'Inter', 'Segoe UI', sans-serif;
color: #e0e0e0;
min-height: 100vh;
padding: 1.5rem;
}
/* ---- Top bar ---- */
.topbar {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.topbar a {
color: #666;
text-decoration: none;
font-size: 0.85rem;
transition: color 0.2s;
}
.topbar a:hover { color: #c8a44a; }
.topbar h1 {
font-size: 1.5rem;
font-weight: 800;
color: #4acc64;
}
.online-dot {
width: 9px; height: 9px;
border-radius: 50%;
background: #cc7b7b;
box-shadow: 0 0 6px #cc7b7b;
display: inline-block;
margin-left: auto;
}
.online-dot.live { background: #7bcc7b; box-shadow: 0 0 6px #7bcc7b; }
.sync-btn {
background: transparent;
border: 1px solid #4acc64;
color: #4acc64;
border-radius: 6px;
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
margin-left: 0.5rem;
}
.sync-btn:hover {
background: rgba(74, 204, 100, 0.1);
}
/* ---- Control Panel ---- */
.panel {
background: #1a1a28;
border: 1px solid #2a2a40;
border-radius: 16px;
padding: 1.75rem;
margin-bottom: 1.5rem;
}
.panel h2 {
font-size: 1rem;
font-weight: 700;
color: #aaa;
text-transform: uppercase;
letter-spacing: 0.07em;
margin-bottom: 1.25rem;
}
/* Toggle switch */
.toggle-row {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.toggle-label { font-size: 1rem; font-weight: 600; }
.toggle {
position: relative;
width: 56px;
height: 30px;
cursor: pointer;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute;
inset: 0;
background: #2a2a3a;
border-radius: 30px;
transition: background 0.3s;
border: 1px solid #3a3a50;
}
.slider::before {
content: '';
position: absolute;
width: 22px; height: 22px;
left: 4px; top: 3px;
background: #666;
border-radius: 50%;
transition: transform 0.3s, background 0.3s;
}
input:checked + .slider { background: rgba(74,204,100,0.25); border-color: #4acc64; }
input:checked + .slider::before { transform: translateX(26px); background: #4acc64; }
/* Loot option selector */
.option-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.option-btn {
background: #12121e;
border: 2px solid #2a2a40;
border-radius: 10px;
padding: 0.75rem 0.5rem;
cursor: pointer;
text-align: center;
transition: border-color 0.2s, background 0.2s;
color: #888;
font-family: inherit;
font-size: 0.9rem;
}
.option-btn .opt-time { font-size: 1.1rem; font-weight: 700; color: #ccc; display: block; }
.option-btn .opt-name { font-size: 0.72rem; color: #666; margin-top: 2px; display: block; }
.option-btn:hover { border-color: #4acc64; background: rgba(74,204,100,0.05); }
.option-btn.selected { border-color: #4acc64; background: rgba(74,204,100,0.12); color: #4acc64; }
.option-btn.selected .opt-time { color: #4acc64; }
.option-btn.selected .opt-name { color: #4acc64bb; }
.save-btn {
background: linear-gradient(135deg, #2a7a3a, #4acc64);
color: #fff;
border: none;
border-radius: 10px;
padding: 0.7rem 2rem;
font-family: inherit;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: opacity 0.2s, transform 0.1s;
}
.save-btn:hover { opacity: 0.9; transform: translateY(-1px); }
.save-btn:active { transform: translateY(0); }
.save-status {
margin-left: 1rem;
font-size: 0.85rem;
color: #4acc64;
opacity: 0;
transition: opacity 0.3s;
}
.save-status.visible { opacity: 1; }
/* Input field */
.input-field {
background: #12121e;
border: 1px solid #2a2a40;
color: #e0e0e0;
border-radius: 6px;
padding: 0.6rem;
width: 100px;
font-family: inherit;
font-size: 0.95rem;
outline: none;
transition: border-color 0.2s;
}
.input-field:focus { border-color: #4acc64; }
/* ---- Farm Table ---- */
.farm-table-wrap { overflow-x: auto; }
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
thead th {
text-align: left;
padding: 0.6rem 1rem;
color: #666;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
border-bottom: 1px solid #2a2a40;
}
tbody tr { border-bottom: 1px solid #1e1e2e; transition: background 0.15s; }
tbody tr:hover { background: rgba(255,255,255,0.03); }
tbody td { padding: 0.75rem 1rem; }
.badge {
display: inline-block;
padding: 2px 10px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 600;
}
.badge.ready { background: rgba(74,204,100,0.2); color: #4acc64; border: 1px solid rgba(74,204,100,0.35); }
.badge.waiting { background: rgba(150,150,200,0.1); color: #888; border: 1px solid #2a2a40; }
.countdown { font-size: 0.82rem; color: #6fcfcf; }
/* Status bar */
.status-bar {
background: rgba(74,204,100,0.08);
border: 1px solid rgba(74,204,100,0.2);
border-radius: 10px;
padding: 0.75rem 1.25rem;
font-size: 0.875rem;
color: #4acc64;
margin-bottom: 1.5rem;
display: none;
}
.status-bar.visible { display: block; }
.status-bar.off {
background: rgba(200,100,100,0.08);
border-color: rgba(200,100,100,0.2);
color: #cc7b7b;
}
/* Empty state */
.empty-state { text-align: center; padding: 3rem 1rem; color: #555; }
.empty-state p { margin-top: 0.5rem; font-size: 0.9rem; }
</style>
</head>
<body>
<div class="topbar">
<a href="/player/{{ player_id }}/{{ world_id }}">← Πίσω</a>
<h1>🌾 Farm Manager</h1>
<span class="online-dot" id="online-dot" title="Κατάσταση Script"></span>
<button class="sync-btn" onclick="requestSync()">Live Sync</button>
</div>
<!-- Status banner -->
<div class="status-bar" id="status-bar"></div>
<!-- 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; align-items: center; gap: 12px; font-weight: 600;">
<span style="font-size: 1.4rem;">📦</span>
<span>
<strong style="color:#ff9933;">Αποθήκη Γεμάτη!</strong>
Όλες οι αποθήκες είναι &gt;95% — το bot παρακάμπτει τη λεηλασία μέχρι να αδειάσει χώρος.
</span>
</div>
<!-- Control Panel -->
<div class="panel">
<h2>⚙️ Ρυθμίσεις</h2>
<div class="toggle-row">
<span class="toggle-label">Αυτόματη Λεηλασία</span>
<label class="toggle">
<input type="checkbox" id="farm-enabled">
<span class="slider"></span>
</label>
<span style="color:#888; font-size:0.85rem;" id="toggle-hint">Ανενεργό</span>
</div>
<div style="margin-bottom: 0.75rem; font-size: 0.85rem; color: #888;">Επίπεδο Λεηλασίας:</div>
<div class="option-grid">
<button class="option-btn selected" data-option="1">
<span class="opt-time">5'</span>
<span class="opt-name">Γρήγορο</span>
</button>
<button class="option-btn" data-option="2">
<span class="opt-time">20'</span>
<span class="opt-name">Κανονικό</span>
</button>
<button class="option-btn" data-option="3">
<span class="opt-time">90'</span>
<span class="opt-name">Αργό</span>
</button>
<button class="option-btn" data-option="4">
<span class="opt-time">4h</span>
<span class="opt-name">Ύπνος</span>
</button>
</div>
<div style="display: flex; gap: 1rem; align-items: center;">
<button class="save-btn" id="save-btn" onclick="saveSettings()">💾 Αποθήκευση</button>
<button class="save-btn" id="manual-loot-btn" onclick="triggerManualLoot()" style="background: linear-gradient(135deg, #2a5a7a, #4a9ccc);">🌾 Λεηλασία Τώρα</button>
<span class="save-status" id="save-status">✓ Στάλθηκε</span>
</div>
</div>
<!-- Farm Upgrade Panel -->
<div class="panel">
<h2>🏰 Αυτόματη Αναβάθμιση Χωριών</h2>
<p style="font-size: 0.85rem; color: #888; margin-bottom: 1rem;">
Το script θα χρησιμοποιήσει αυτόματα τους πόντους μάχης σας για να ξεκλειδώσει νέα χωριά και να τα αναβαθμίσει μέχρι το επίπεδο 6.<br>
Επιλέξτε πόσους πόντους μάχης θέλετε να κρατήσετε ως <strong>όριο ασφαλείας</strong>. Το script θα ξοδέψει μόνο τους πόντους ΠΑΝΩ από αυτό το όριο.
</p>
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem;">
<label for="kp-threshold" style="font-size: 0.95rem; font-weight: 600;">Ελάχιστοι Πόντοι (Όριο):</label>
<input type="number" id="kp-threshold" class="input-field" value="0" min="0" step="10">
</div>
<div style="display: flex; gap: 1rem;">
<button class="save-btn" id="unlock-btn" onclick="triggerUpgrade('unlock')" style="flex: 1; background: linear-gradient(135deg, #7a5c2a, #cca04a);">🔓 Μόνο Ξεκλείδωμα</button>
<button class="save-btn" id="upgrade-btn" onclick="triggerUpgrade('upgrade')" style="flex: 1;">🚀 Μόνο Αναβάθμιση</button>
</div>
<div style="margin-top: 10px;">
<span class="save-status" id="upgrade-status">Εντολή εστάλη!</span>
</div>
</div>
<!-- Bandit Camp Panel -->
<div class="panel">
<h2>🏕️ Αυτόματο Bandit Camp</h2>
<p style="font-size:0.85rem;color:#888;margin-bottom:1rem;">
Το bot επιτίθεται αυτόματα στο στρατόπεδο ληστών και διεκδικεί αμοιβές.<br>
Ελέγχει κάθε <strong>1222 λεπτά</strong> (τυχαίο) — ανθρώπινος ρυθμός.
</p>
<div class="toggle-row" style="margin-bottom:1rem;">
<span class="toggle-label">Αυτόματη Επίθεση</span>
<label class="toggle">
<input type="checkbox" id="bootcamp-enabled">
<span class="slider"></span>
</label>
<span style="color:#888;font-size:0.85rem;" id="bootcamp-hint">Ανενεργό</span>
</div>
<div class="toggle-row" style="margin-bottom:1.5rem;">
<span class="toggle-label">Συμπ. Αμυντικές Μονάδες (Σπαθ/Τοξ)</span>
<label class="toggle">
<input type="checkbox" id="bootcamp-use-def">
<span class="slider"></span>
</label>
</div>
<button class="save-btn" onclick="saveBotSettings()">💾 Αποθήκευση</button>
<span class="save-status" id="bot-save-status">✓ Αποθηκεύτηκε</span>
<div style="margin-top: 1rem; border-top: 1px solid #1a3040; padding-top: 1rem;">
<button class="save-btn" id="bootcamp-attack-btn" onclick="attackBootcampNow()" style="background: linear-gradient(135deg, #7a2a2a, #cc4a4a); width: 100%;">⚔️ Επίθεση Τώρα</button>
<div style="text-align: center; margin-top: 5px;"><span class="save-status" id="bootcamp-attack-status">Εντολή εστάλη!</span></div>
</div>
<h3 style="margin-top:1.5rem;font-size:0.9rem;color:#aaa;">📋 Ιστορικό</h3>
<div id="bootcamp-log" style="background:#0a1520;border:1px solid #1a3040;border-radius:8px;padding:10px;max-height:180px;overflow-y:auto;font-size:0.78rem;font-family:monospace;color:#8ab4d0;">
<span style="color:#444;">Αναμονή δεδομένων...</span>
</div>
</div>
<!-- Rural Trade Panel -->
<div class="panel">
<h2>🔄 Αυτόματο Trade Χωριών</h2>
<p style="font-size:0.85rem;color:#888;margin-bottom:1rem;">
Ενεργοποιείται <strong>μόνο όταν μια κατασκευή κολλάει λόγω πόρων</strong>.<br>
Ψάχνει χωριά στο νησί που προσφέρουν τον ελλείποντα πόρο και κάνει trade.<br>
Ελέγχει κάθε <strong>2545 λεπτά</strong> (τυχαίο).
</p>
<div class="toggle-row" style="margin-bottom:1rem;">
<span class="toggle-label">Αυτόματο Trade</span>
<label class="toggle">
<input type="checkbox" id="rural-trade-enabled">
<span class="slider"></span>
</label>
<span style="color:#888;font-size:0.85rem;" id="rural-trade-hint">Ανενεργό</span>
</div>
<div style="margin-bottom:0.75rem;font-size:0.85rem;color:#888;">Ελάχιστο Ratio Trade (τιμή χωριού):</div>
<div class="option-grid" style="margin-bottom:1.5rem;">
<button class="option-btn" data-ratio="1" onclick="selectRatio(1)"><span class="opt-time">0.25</span><span class="opt-name">Ελάχ.</span></button>
<button class="option-btn" data-ratio="2" onclick="selectRatio(2)"><span class="opt-time">0.50</span><span class="opt-name">Χαμηλό</span></button>
<button class="option-btn selected" data-ratio="3" onclick="selectRatio(3)"><span class="opt-time">0.75</span><span class="opt-name">Κανον.</span></button>
<button class="option-btn" data-ratio="4" onclick="selectRatio(4)"><span class="opt-time">1.00</span><span class="opt-name">Υψηλό</span></button>
<button class="option-btn" data-ratio="5" onclick="selectRatio(5)"><span class="opt-time">1.25</span><span class="opt-name">Μέγιστο</span></button>
</div>
<button class="save-btn" onclick="saveBotSettings()">💾 Αποθήκευση</button>
<h3 style="margin-top:1.5rem;font-size:0.9rem;color:#aaa;">📋 Ιστορικό</h3>
<div id="rural-trade-log" style="background:#0a1520;border:1px solid #1a3040;border-radius:8px;padding:10px;max-height:180px;overflow-y:auto;font-size:0.78rem;font-family:monospace;color:#8ab4d0;">
<span style="color:#444;">Αναμονή δεδομένων...</span>
</div>
</div>
<!-- Farm Status Table -->
<div class="panel">
<h2>🏘️ Κατάσταση Χωριών</h2>
<div class="farm-table-wrap">
<table>
<thead>
<tr>
<th>Πόλη</th>
<th>Έτοιμα</th>
<th>Σύνολο</th>
<th>Επόμενο</th>
<th>Τελευταία Λεηλασία</th>
</tr>
</thead>
<tbody id="farm-table-body">
<tr><td colspan="5"><div class="empty-state"><p>Φόρτωση δεδομένων...</p></div></td></tr>
</tbody>
</table>
</div>
</div>
<script>
const PLAYER_ID = '{{ player_id }}';
const WORLD_ID = '{{ world_id }}';
let selectedOption = 1;
// -- Loot option buttons --
document.querySelectorAll('.option-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.option-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
selectedOption = parseInt(btn.dataset.option);
});
});
// -- Toggle hint text --
document.getElementById('farm-enabled').addEventListener('change', function () {
document.getElementById('toggle-hint').textContent = this.checked ? '🟢 Ενεργό' : 'Ανενεργό';
updateStatusBar(this.checked);
});
function updateStatusBar(enabled) {
const bar = document.getElementById('status-bar');
if (enabled) {
bar.className = 'status-bar visible';
bar.textContent = '🤖 Ο αυτόματος farmer είναι ενεργός. Το script θα λεηλατεί χωριά με τυχαίες καθυστερήσεις.';
} else {
bar.className = 'status-bar visible off';
bar.textContent = '⏸ Η αυτόματη λεηλασία είναι ανενεργή.';
}
}
// -- Save settings --
function saveSettings() {
const enabled = document.getElementById('farm-enabled').checked;
fetch('/dashboard/farm-settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ player_id: PLAYER_ID, world_id: WORLD_ID, enabled, loot_option: selectedOption })
})
.then(r => r.json())
.then(() => {
const s = document.getElementById('save-status');
s.classList.add('visible');
setTimeout(() => s.classList.remove('visible'), 2500);
});
}
// -- Load current settings --
function loadSettings() {
fetch(`/dashboard/farm-settings?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`)
.then(r => r.json())
.then(cfg => {
document.getElementById('farm-enabled').checked = cfg.enabled;
document.getElementById('toggle-hint').textContent = cfg.enabled ? '🟢 Ενεργό' : 'Ανενεργό';
if (cfg.enabled) updateStatusBar(true);
selectedOption = cfg.loot_option || 1;
document.querySelectorAll('.option-btn').forEach(b => {
b.classList.toggle('selected', parseInt(b.dataset.option) === selectedOption);
});
});
}
// -- Load farm data table --
function timeAgo(isoStr) {
if (!isoStr) return '—';
const diff = Math.floor((Date.now() - new Date(isoStr + (isoStr.endsWith('Z') ? '' : 'Z'))) / 1000);
if (diff < 60) return `${diff}δ πριν`;
if (diff < 3600) return `${Math.floor(diff / 60)}λ πριν`;
return `${Math.floor(diff / 3600)}ω πριν`;
}
function loadFarmData() {
fetch(`/dashboard/farm-data?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`)
.then(r => r.json())
.then(resp => {
const data = resp.towns || [];
const lastFarmed = resp.last_farmed_at ? timeAgo(resp.last_farmed_at) : '—';
const tbody = document.getElementById('farm-table-body');
if (!data || data.length === 0) {
tbody.innerHTML = '<tr><td colspan="5"><div class="empty-state">🌱 <p>Δεν υπάρχουν δεδομένα χωριών ακόμη.<br>Βεβαιώσου ότι το script v3.3+ τρέχει στο παιχνίδι.</p></div></td></tr>';
return;
}
const now = Math.floor(Date.now() / 1000);
tbody.innerHTML = data.map((t, idx) => {
const readyBadge = t.ready_farms > 0
? `<span class="badge ready">✓ ${t.ready_farms} Έτοιμα</span>`
: `<span class="badge waiting">Αναμονή</span>`;
let nextStr = '—';
if (t.next_ready_at) {
const diff = t.next_ready_at - now;
if (diff > 0) {
const m = Math.floor(diff / 60);
const s = diff % 60;
nextStr = `<span class="countdown">${m}λ ${s}δ</span>`;
} else {
nextStr = '<span class="countdown">Τώρα</span>';
}
}
// Show last farmed only in first row — same value for all rows
const lastFarmedCell = idx === 0
? `<td rowspan="${data.length}" style="color:#4acc64;font-size:0.82rem;vertical-align:middle;">${lastFarmed}</td>`
: '';
return `<tr>
<td><strong>${t.town_name}</strong></td>
<td>${readyBadge}</td>
<td><span style="color:#888">${t.total_farms}</span></td>
<td>${nextStr}</td>
${lastFarmedCell}
</tr>`;
}).join('');
});
}
// -- Online status check --
function checkOnline() {
fetch(`/dashboard/client-status?player_id=${PLAYER_ID}`)
.then(r => r.json())
.then(d => {
const dot = document.getElementById('online-dot');
dot.className = 'online-dot' + (d.online ? ' live' : '');
dot.title = d.online ? 'Script Online' : 'Script Offline';
});
}
// -- Live Sync --
function requestSync() {
const btn = document.querySelector('.sync-btn');
const originalText = btn.innerText;
btn.innerText = '⏳ Syncing...';
btn.disabled = true;
fetch(`/api/sync-request?player_id=${PLAYER_ID}`, { method: 'POST' })
.then(r => r.json())
.then(data => {
setTimeout(() => {
loadFarmData();
btn.innerText = originalText;
btn.disabled = false;
}, 1500);
});
}
// -- Trigger Upgrade / Unlock --
function triggerUpgrade(actionType) {
const threshold = document.getElementById('kp-threshold').value;
const btn = actionType === 'unlock' ? document.getElementById('unlock-btn') : document.getElementById('upgrade-btn');
const originalText = btn.innerText;
btn.innerText = '⏳ Αποστολή...';
btn.disabled = true;
fetch('/dashboard/commands', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
player_id: PLAYER_ID,
town_id: WORLD_ID ? `0_${WORLD_ID}` : "0",
type: 'farm_upgrade',
payload: { threshold: threshold, action_type: actionType }
})
})
.then(r => r.json())
.then(() => {
btn.innerText = originalText;
btn.disabled = false;
const s = document.getElementById('upgrade-status');
s.classList.add('visible');
setTimeout(() => s.classList.remove('visible'), 3000);
});
}
// -- Trigger Manual Loot --
function triggerManualLoot() {
const btn = document.getElementById('manual-loot-btn');
const originalText = btn.innerText;
btn.innerText = '⏳ Αποστολή...';
btn.disabled = true;
fetch('/dashboard/commands', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
player_id: PLAYER_ID,
town_id: WORLD_ID ? `0_${WORLD_ID}` : "0",
type: 'farm_loot',
payload: { loot_option: selectedOption }
})
})
.then(r => r.json())
.then(() => {
btn.innerText = '✓ Εστάλη!';
setTimeout(() => {
btn.innerText = originalText;
btn.disabled = false;
}, 2000);
});
}
function attackBootcampNow() {
const btn = document.getElementById('bootcamp-attack-btn');
const status = document.getElementById('bootcamp-attack-status');
const originalText = btn.innerText;
btn.innerText = '⏳ Αποστολή...';
btn.disabled = true;
fetch('/dashboard/bootcamp-attack-now', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ player_id: PLAYER_ID, world_id: WORLD_ID })
})
.then(r => r.json())
.then(() => {
btn.innerText = '✓ Εστάλη!';
status.style.opacity = '1';
setTimeout(() => {
btn.innerText = originalText;
btn.disabled = false;
status.style.opacity = '0';
}, 2000);
});
}
async function checkWarehouseStatus() {
try {
const res = await fetch(`/api/farm_status?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`);
const data = await res.json();
const banner = document.getElementById('warehouse-full-banner');
if (banner) banner.style.display = data.warehouse_full ? 'flex' : 'none';
} catch(e) {}
}
// ── Bot Settings (Bootcamp + Rural Trade) ─────────────────────
let selectedRatio = 3;
function selectRatio(n) {
selectedRatio = n;
document.querySelectorAll('[data-ratio]').forEach(b => {
b.classList.toggle('selected', parseInt(b.dataset.ratio) === n);
});
}
function loadBotSettings() {
fetch(`/dashboard/bot-settings?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`)
.then(r => r.json())
.then(cfg => {
document.getElementById('bootcamp-enabled').checked = !!cfg.bootcamp_enabled;
document.getElementById('bootcamp-hint').textContent = cfg.bootcamp_enabled ? '🟢 Ενεργό' : 'Ανενεργό';
document.getElementById('bootcamp-use-def').checked = !!cfg.bootcamp_use_def;
document.getElementById('rural-trade-enabled').checked = !!cfg.rural_trade_enabled;
document.getElementById('rural-trade-hint').textContent = cfg.rural_trade_enabled ? '🟢 Ενεργό' : 'Ανενεργό';
selectRatio(cfg.rural_trade_ratio || 3);
});
}
function saveBotSettings() {
fetch('/dashboard/bot-settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
player_id: PLAYER_ID,
world_id: WORLD_ID,
bootcamp_enabled: document.getElementById('bootcamp-enabled').checked,
bootcamp_use_def: document.getElementById('bootcamp-use-def').checked,
rural_trade_enabled: document.getElementById('rural-trade-enabled').checked,
rural_trade_ratio: selectedRatio
})
}).then(r => r.json()).then(() => {
// Update hints
document.getElementById('bootcamp-hint').textContent =
document.getElementById('bootcamp-enabled').checked ? '🟢 Ενεργό' : 'Ανενεργό';
document.getElementById('rural-trade-hint').textContent =
document.getElementById('rural-trade-enabled').checked ? '🟢 Ενεργό' : 'Ανενεργό';
const s = document.getElementById('bot-save-status');
s.classList.add('visible');
setTimeout(() => s.classList.remove('visible'), 2500);
});
}
// Wire toggle hints live
document.getElementById('bootcamp-enabled').addEventListener('change', function() {
document.getElementById('bootcamp-hint').textContent = this.checked ? '🟢 Ενεργό' : 'Ανενεργό';
});
document.getElementById('rural-trade-enabled').addEventListener('change', function() {
document.getElementById('rural-trade-hint').textContent = this.checked ? '🟢 Ενεργό' : 'Ανενεργό';
});
// ── Bot Logs ───────────────────────────────────────────────────
function renderBotLog(containerId, entries) {
const el = document.getElementById(containerId);
if (!entries || entries.length === 0) {
el.innerHTML = '<span style="color:#444;">Δεν υπάρχουν εγγραφές ακόμη.</span>';
return;
}
el.innerHTML = entries.map(e => {
const t = new Date(e.created_at + 'Z').toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
return `<div><span style="color:#3a6a8a;">[${t}]</span> ${e.message}</div>`;
}).join('');
}
function loadBotLogs() {
fetch(`/dashboard/bot-logs?player_id=${PLAYER_ID}&world_id=${WORLD_ID}&feature=bootcamp`)
.then(r => r.json()).then(data => renderBotLog('bootcamp-log', data));
fetch(`/dashboard/bot-logs?player_id=${PLAYER_ID}&world_id=${WORLD_ID}&feature=rural_trade`)
.then(r => r.json()).then(data => renderBotLog('rural-trade-log', data));
}
// -- Boot --
loadSettings();
loadBotSettings();
loadFarmData();
loadBotLogs();
checkOnline();
checkWarehouseStatus();
setInterval(loadFarmData, 15000);
setInterval(loadBotLogs, 15000);
setInterval(checkOnline, 20000);
setInterval(checkWarehouseStatus, 20000);
</script>
</body>
</html>