538 lines
17 KiB
HTML
538 lines
17 KiB
HTML
<!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 }}">← Πίσω</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; display: flex; align-items: center; gap: 12px; font-weight: 600;">
|
||
<span style="font-size: 1.4rem;">📦</span>
|
||
<span>
|
||
<strong style="color:#ff9933;">Αποθήκη Γεμάτη!</strong>
|
||
Όλες οι αποθήκες είναι >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>
|
||
|
||
<!-- Farm Status Table -->
|
||
<div class="panel">
|
||
<h2>🏘️ Κατάσταση Χωριών</h2>
|
||
<div class="farm-table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Πόλη</th>
|
||
<th>Έτοιμα</th>
|
||
<th>Σύνολο</th>
|
||
<th>Επόμενο</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="farm-table-body">
|
||
<tr><td colspan="4"><div class="empty-state">⏳ <p>Φόρτωση δεδομένων...</p></div></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const PLAYER_ID = '{{ player_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, 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}`)
|
||
.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 loadFarmData() {
|
||
fetch(`/dashboard/farm-data?player_id=${PLAYER_ID}`)
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
const tbody = document.getElementById('farm-table-body');
|
||
if (!data || data.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="4"><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 => {
|
||
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>';
|
||
}
|
||
}
|
||
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>
|
||
</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: 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: 0,
|
||
type: 'farm_loot',
|
||
payload: { loot_option: selectedOption }
|
||
})
|
||
})
|
||
.then(r => r.json())
|
||
.then(() => {
|
||
btn.innerText = '✓ Εστάλη!';
|
||
setTimeout(() => {
|
||
btn.innerText = originalText;
|
||
btn.disabled = false;
|
||
}, 2000);
|
||
});
|
||
}
|
||
|
||
// -- Warehouse full notice --
|
||
async function checkWarehouseStatus() {
|
||
try {
|
||
const res = await fetch(`/api/farm_status?player_id=${PLAYER_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) {}
|
||
}
|
||
|
||
// -- Boot --
|
||
loadSettings();
|
||
loadFarmData();
|
||
checkOnline();
|
||
checkWarehouseStatus();
|
||
setInterval(loadFarmData, 15000);
|
||
setInterval(checkOnline, 20000);
|
||
setInterval(checkWarehouseStatus, 20000);
|
||
</script>
|
||
</body>
|
||
</html>
|