CryptoFox-Mukan-Edition/src/web/bots.html
Mukan Erkin 2ff8d57c08 feat(mse): shared bot modal component + pagination on positions/bots
- Extract bot creation modal to _bot_modal.html partial (TomSelect, min_notional display)
- bots.html and index.html now include the partial via {% include %}
- Remove duplicate modal JS from bots.html; add onBotCreated/onModeChange hooks
- Add 20-item pagination to positions.html (open + closed tables)
- positions.html: remove redundant loadMode (handled by _header.html partial)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 10:45:06 +03:00

237 lines
13 KiB
HTML
Raw 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="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Botlar — CryptoFox Mukan Edition</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select@2/dist/css/tom-select.min.css">
<script src="https://cdn.jsdelivr.net/npm/tom-select@2/dist/js/tom-select.complete.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f0f13; --surface: #1a1a22; --border: #2a2a38;
--text: #e0e0f0; --muted: #666; --accent: #6c63ff;
--green: #2ecc71; --red: #e74c3c; --yellow: #f39c12;
}
body { background: var(--bg); color: var(--text); font-family: 'Segoe UI', system-ui, sans-serif; font-size: 14px; min-height: 100vh; }
/* ── HEADER ── */
header { background: var(--surface); border-bottom: 1px solid var(--border); padding: 0 24px; height: 52px; display: flex; align-items: center; gap: 16px; }
.logo { font-size: 16px; font-weight: 700; }
.logo-crypto { color: #d0d0d0; } .logo-fox { color: #00d4ff; }
.h-sep { width: 1px; height: 20px; background: var(--border); }
nav { display: flex; gap: 4px; }
.nav-link { padding: 5px 12px; border-radius: 5px; font-size: 13px; color: var(--muted); text-decoration: none; transition: all .15s; }
.nav-link:hover { color: var(--text); background: rgba(255,255,255,.05); }
.nav-link.active { color: var(--text); background: rgba(108,99,255,.15); }
.spacer { flex: 1; }
.btn { padding: 5px 12px; border-radius: 5px; border: 1px solid var(--border); background: transparent; color: var(--text); font-size: 12px; cursor: pointer; transition: all .15s; white-space: nowrap; }
.btn:hover { background: rgba(255,255,255,.05); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: #5a52d5; }
.btn-start { border-color: var(--green); color: var(--green); }
.btn-start:hover { background: rgba(46,204,113,.1); }
.btn-stop { border-color: var(--yellow); color: var(--yellow); }
.btn-stop:hover { background: rgba(243,156,18,.1); }
.btn-delete { border-color: var(--red); color: var(--red); }
.btn-delete:hover { background: rgba(231,76,60,.1); }
.btn-logout { border-color: var(--red); color: var(--red); font-size: 12px; }
.btn-logout:hover { background: rgba(231,76,60,.1); }
/* ── MODE TOGGLE ── */
.mode-toggle { display: flex; align-items: center; gap: 4px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 3px; }
.mode-btn { padding: 3px 12px; border-radius: 4px; border: none; background: transparent; color: var(--muted); font-size: 11px; font-weight: 600; cursor: pointer; transition: all .15s; }
.mode-btn.active-live { background: rgba(46,204,113,.15); color: var(--green); }
.mode-btn.active-testnet { background: rgba(243,156,18,.15); color: var(--yellow); }
/* ── MAIN ── */
main { padding: 24px; max-width: 1400px; margin: 0 auto; }
.page-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.page-title { font-size: 16px; font-weight: 600; }
/* ── GROUP HEADER ── */
.group-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
.group-label .count { background: rgba(255,255,255,.07); color: var(--text); padding: 1px 7px; border-radius: 10px; font-size: 11px; }
.group-running .group-label { color: var(--green); }
.group-section { margin-bottom: 32px; }
/* ── CARDS GRID ── */
.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 14px; }
/* ── BOT CARD ── */
.bot-card {
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
display: flex; flex-direction: column; gap: 0; overflow: hidden;
transition: border-color .15s;
}
.bot-card.running { border-color: rgba(46,204,113,.35); }
.bot-card:hover { border-color: rgba(108,99,255,.4); }
.card-header { padding: 14px 16px 10px; display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.card-title { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.card-name { font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-meta { display: flex; align-items: center; gap: 6px; }
.card-symbol { font-size: 12px; color: var(--muted); font-weight: 500; }
.card-tf { font-size: 11px; color: var(--muted); background: rgba(255,255,255,.06); padding: 1px 6px; border-radius: 4px; }
.card-badges { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.badge-running { background: rgba(46,204,113,.15); color: var(--green); }
.badge-stopped { background: rgba(136,136,136,.1); color: var(--muted); }
.badge-testnet { background: rgba(243,156,18,.12); color: var(--yellow); font-size: 10px; }
.card-stats { padding: 10px 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.stat { display: flex; flex-direction: column; gap: 2px; }
.stat-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.stat-value { font-size: 13px; font-weight: 600; }
.stat-value.green { color: var(--green); }
.card-actions { padding: 10px 16px; display: flex; align-items: center; gap: 8px; }
.card-detail-link { font-size: 12px; color: var(--muted); text-decoration: none; margin-left: auto; transition: color .15s; }
.card-detail-link:hover { color: var(--accent); }
/* ── EMPTY ── */
.empty-state { color: var(--muted); font-size: 13px; padding: 20px 0; }
/* ── MODAL ── */
#modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 100; }
#modal-overlay.open { display: flex; }
.modal { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; width: 480px; max-width: 95vw; }
.modal-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
.modal-header h2 { font-size: 15px; font-weight: 600; }
.modal-close { background: none; border: none; color: var(--muted); font-size: 20px; cursor: pointer; }
.modal-close:hover { color: var(--text); }
.modal-body { padding: 20px; display: flex; flex-direction: column; gap: 14px; }
.modal-footer { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
.form-group { display: flex; flex-direction: column; gap: 5px; }
.form-group label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.form-group input, .form-group select { background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 7px 10px; color: var(--text); font-size: 13px; width: 100%; }
.form-group input:focus, .form-group select:focus { outline: none; border-color: var(--accent); }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ts-wrapper .ts-control, .ts-wrapper.single .ts-control { background: var(--bg) !important; border-color: var(--border) !important; color: var(--text) !important; min-height: 34px; padding: 4px 10px; border-radius: 5px; box-shadow: none !important; }
.ts-wrapper.focus .ts-control { border-color: var(--accent) !important; box-shadow: none !important; }
.ts-wrapper .ts-control input { background: transparent !important; color: var(--text) !important; outline: none !important; }
.ts-dropdown, .ts-dropdown.single { background: var(--surface) !important; border-color: var(--border) !important; color: var(--text) !important; z-index: 200; }
.ts-dropdown .option { padding: 7px 12px; color: var(--text) !important; }
.ts-dropdown .option:hover, .ts-dropdown .option.active { background: rgba(108,99,255,.2) !important; }
.ts-wrapper .ts-control .item { color: var(--text) !important; background: rgba(108,99,255,.15) !important; border-radius: 3px; padding: 1px 6px; border: none !important; }
.pagination { display: flex; align-items: center; gap: 4px; padding: 12px 0 4px; flex-wrap: wrap; }
.pg-btn { padding: 4px 10px; border-radius: 5px; border: 1px solid var(--border); background: transparent; color: var(--muted); font-size: 12px; cursor: pointer; transition: all .15s; }
.pg-btn:hover { background: rgba(255,255,255,.05); color: var(--text); }
.pg-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.pg-info { font-size: 11px; color: var(--muted); margin-left: 8px; }
</style>
</head>
<body>
{% include "_header.html" %}
<main>
<div class="page-toolbar">
<span class="page-title">Botlar</span>
<button class="btn btn-primary" onclick="openModal()">+ Yeni Bot</button>
</div>
<div class="group-section group-running">
<div class="group-label">Çalışıyor <span class="count" id="count-running">0</span></div>
<div class="cards-grid" id="grid-running"><div class="empty-state">Çalışan bot yok</div></div>
</div>
<div class="group-section">
<div class="group-label">Durdurulmuş <span class="count" id="count-stopped">0</span></div>
<div class="cards-grid" id="grid-stopped"><div class="empty-state">Durdurulan bot yok</div></div>
</div>
</main>
{% include "_bot_modal.html" %}
<script>
async function api(method, path, body) {
const res = await fetch('/api' + path, {
method, credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 401) { window.location.href = '/'; throw new Error('Unauthorized'); }
return res;
}
function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function onBotCreated() { loadBots(); }
function onModeChange() { loadBots(); }
// ── Bots ──
async function loadBots() {
const res = await api('GET', '/bots');
const bots = await res.json();
const running = bots.filter(b => b.running);
const stopped = bots.filter(b => !b.running);
document.getElementById('count-running').textContent = running.length;
document.getElementById('count-stopped').textContent = stopped.length;
renderGrid('grid-running', running, 'Çalışan bot yok');
renderGrid('grid-stopped', stopped, 'Durdurulan bot yok');
}
function renderGrid(id, bots, emptyMsg) {
const grid = document.getElementById(id);
if (!bots.length) { grid.innerHTML = `<div class="empty-state">${emptyMsg}</div>`; return; }
grid.innerHTML = bots.map(b => `
<div class="bot-card ${b.running ? 'running' : ''}" id="card-${b.id}">
<div class="card-header">
<div class="card-title">
<div class="card-name">${esc(b.name)}</div>
<div class="card-meta">
<span class="card-symbol">${esc(b.symbol)}</span>
<span class="card-tf">${esc(b.timeframe)}</span>
</div>
</div>
<div class="card-badges">
<span class="badge ${b.running ? 'badge-running' : 'badge-stopped'}">${b.running ? '● Çalışıyor' : '● Durdu'}</span>
${b.testnet ? '<span class="badge badge-testnet">TESTNET</span>' : ''}
</div>
</div>
<div class="card-stats">
<div class="stat">
<span class="stat-label">İşlem Miktarı</span>
<span class="stat-value">${b.usdt_amount} USDT</span>
</div>
<div class="stat">
<span class="stat-label">Kar Hedefi</span>
<span class="stat-value green">%${b.profit_percent}</span>
</div>
</div>
<div class="card-actions">
${b.running
? `<button class="btn btn-stop" onclick="stopBot('${b.id}')">Durdur</button>`
: `<button class="btn btn-start" onclick="startBot('${b.id}')">Başlat</button>`
}
<button class="btn btn-delete" onclick="deleteBot('${b.id}','${esc(b.name)}')">Sil</button>
<a href="/bots/${b.id}" class="card-detail-link">Detay →</a>
</div>
</div>
`).join('');
}
async function startBot(id) {
await api('POST', `/bots/${id}/start`);
loadBots();
}
async function stopBot(id) {
await api('POST', `/bots/${id}/stop`);
loadBots();
}
async function deleteBot(id, name) {
if (!confirm(`"${name}" botunu silmek istiyor musun?`)) return;
await api('DELETE', `/bots/${id}`);
loadBots();
}
async function logout() {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
window.location.href = '/';
}
loadBots();
</script>
</body>
</html>