CryptoFox-Mukan-Edition/src/web/index.html

337 lines
15 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>Mukan Special 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: #888;
--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 { background: var(--surface); border-bottom: 1px solid var(--border); padding: 16px 24px; display: flex; align-items: center; gap: 12px; }
header h1 { font-size: 18px; font-weight: 600; color: var(--accent); }
header .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
header .status-dot.connected { background: var(--green); }
main { padding: 24px; max-width: 1400px; margin: 0 auto; display: flex; flex-direction: column; gap: 24px; }
section { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.section-header { padding: 14px 18px; border-bottom: 1px solid var(--border); font-weight: 600; font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); display: flex; align-items: center; justify-content: space-between; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px 18px; text-align: left; border-bottom: 1px solid var(--border); }
th { font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 500; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: rgba(255,255,255,0.02); }
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.badge-running { background: rgba(46,204,113,0.15); color: var(--green); }
.badge-stopped { background: rgba(136,136,136,0.1); color: var(--muted); }
.btn { padding: 5px 12px; border-radius: 5px; border: 1px solid var(--border); background: transparent; color: var(--text); font-size: 12px; cursor: pointer; transition: all 0.15s; }
.btn:hover { background: rgba(255,255,255,0.05); }
.btn-start { border-color: var(--green); color: var(--green); }
.btn-start:hover { background: rgba(46,204,113,0.1); }
.btn-stop { border-color: var(--yellow); color: var(--yellow); }
.btn-stop:hover { background: rgba(243,156,18,0.1); }
.btn-delete { border-color: var(--red); color: var(--red); }
.btn-delete:hover { background: rgba(231,76,60,0.1); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: #5a52d5; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; padding: 18px; }
.form-group { display: flex; flex-direction: column; gap: 5px; }
.form-group label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.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; }
.form-group input:focus, .form-group select:focus { outline: none; border-color: var(--accent); }
.form-group select option { background: var(--surface); }
.form-actions { padding: 0 18px 18px; }
.empty { padding: 24px 18px; color: var(--muted); font-size: 13px; text-align: center; }
#auth-overlay { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 100; }
.auth-box { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 32px; width: 320px; display: flex; flex-direction: column; gap: 16px; }
.auth-box h2 { font-size: 16px; color: var(--accent); }
.auth-box input { background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 9px 12px; color: var(--text); font-size: 14px; width: 100%; }
.auth-box input:focus { outline: none; border-color: var(--accent); }
.auth-error { color: var(--red); font-size: 12px; display: none; }
.ts-wrapper .ts-control { background: var(--bg); border-color: var(--border); color: var(--text); min-height: 32px; padding: 4px 10px; border-radius: 5px; }
.ts-wrapper.focus .ts-control { border-color: var(--accent); box-shadow: none; }
.ts-dropdown { background: var(--surface); border-color: var(--border); color: var(--text); }
.ts-dropdown .option { padding: 7px 12px; }
.ts-dropdown .option.active { background: rgba(108,99,255,0.2); color: var(--text); }
.ts-wrapper .ts-control .item { color: var(--text); background: rgba(108,99,255,0.15); border-radius: 3px; padding: 1px 6px; }
</style>
</head>
<body>
<div id="auth-overlay">
<div class="auth-box">
<h2>Mukan Special Edition</h2>
<input type="password" id="token-input" placeholder="Auth token" />
<button class="btn btn-primary" onclick="login()">Giriş</button>
<span class="auth-error" id="auth-error">Geçersiz token</span>
</div>
</div>
<header>
<h1>MSE Dashboard</h1>
<span class="status-dot" id="sse-dot"></span>
<span id="sse-label" style="font-size:12px; color:var(--muted)">bağlanıyor...</span>
</header>
<main>
<section>
<div class="section-header">Botlar <span id="bot-count" style="color:var(--text)">0</span></div>
<table id="bots-table">
<thead><tr><th>İsim</th><th>Sembol</th><th>Timeframe</th><th>USDT</th><th>Kar %</th><th>Testnet</th><th>Durum</th><th></th></tr></thead>
<tbody id="bots-body"><tr><td colspan="8" class="empty">Yükleniyor...</td></tr></tbody>
</table>
</section>
<section>
<div class="section-header">Yeni Bot Ekle</div>
<div class="form-grid">
<div class="form-group"><label>İsim</label><input id="f-name" placeholder="DOGE Bot" /></div>
<div class="form-group">
<label>Coin (USDT çifti)</label>
<select id="f-symbol"><option value="">Yükleniyor...</option></select>
</div>
<div class="form-group"><label>Timeframe</label>
<select id="f-timeframe">
<option value="1m">1m</option>
<option value="5m" selected>5m</option>
<option value="15m">15m</option>
<option value="30m">30m</option>
<option value="1h">1h</option>
<option value="4h">4h</option>
<option value="1d">1d</option>
</select>
</div>
<div class="form-group">
<label>USDT Miktarı <span id="f-min-label" style="color:var(--muted);font-size:10px"></span></label>
<input id="f-usdt" type="number" step="0.1" placeholder="Min. seçilecek" />
</div>
<div class="form-group"><label>Kar %</label><input id="f-profit" type="number" step="0.1" placeholder="2" /></div>
<div class="form-group"><label>Testnet</label>
<select id="f-testnet">
<option value="false">Hayır (Gerçek)</option>
<option value="true">Evet (Testnet)</option>
</select>
</div>
</div>
<div class="form-actions"><button class="btn btn-primary" onclick="createBot()">Bot Ekle</button></div>
</section>
<section>
<div class="section-header">ık Pozisyonlar</div>
<table>
<thead><tr><th>Bot</th><th>Sembol</th><th>Alış</th><th>Hedef</th><th>Miktar</th><th>Zaman</th></tr></thead>
<tbody id="positions-body"><tr><td colspan="6" class="empty">ık pozisyon yok</td></tr></tbody>
</table>
</section>
<section>
<div class="section-header">Kapalı İşlemler</div>
<table>
<thead><tr><th>Bot</th><th>Sembol</th><th>Alış</th><th>Hedef</th><th>Miktar</th><th>Durum</th><th>Zaman</th></tr></thead>
<tbody id="closed-body"><tr><td colspan="7" class="empty">Henüz işlem yok</td></tr></tbody>
</table>
</section>
</main>
<script>
let AUTH_TOKEN = localStorage.getItem('mse_token') || '';
let sseSource = null;
let symbolSelect = null;
function login() {
const t = document.getElementById('token-input').value.trim();
if (!t) return;
AUTH_TOKEN = t;
localStorage.setItem('mse_token', t);
tryConnect();
}
async function api(method, path, body) {
const res = await fetch('/api' + path, {
method,
headers: { 'Authorization': 'Bearer ' + AUTH_TOKEN, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 401) { showAuthOverlay(); throw new Error('Unauthorized'); }
return res;
}
function showAuthOverlay() {
document.getElementById('auth-overlay').style.display = 'flex';
}
async function tryConnect() {
const res = await fetch('/api/bots', { headers: { 'Authorization': 'Bearer ' + AUTH_TOKEN } });
if (res.status === 401) {
document.getElementById('auth-error').style.display = 'block';
return;
}
document.getElementById('auth-overlay').style.display = 'none';
loadAll();
connectSSE();
}
async function loadAll() {
loadBots();
loadPositions();
loadClosed();
loadSymbols();
}
async function loadSymbols() {
const res = await api('GET', '/symbols');
const symbols = await res.json();
symbolSelect = new TomSelect('#f-symbol', {
valueField: 'symbol',
labelField: 'symbol',
searchField: 'symbol',
options: symbols,
placeholder: 'Ara: DOGE, BTC...',
render: {
option: (data) =>
`<div style="display:flex;justify-content:space-between;align-items:center">
<strong>${data.symbol}</strong>
<span style="color:#888;font-size:11px">min ${data.min_notional} USDT</span>
</div>`,
item: (data) => `<div>${data.symbol}</div>`,
},
onChange(value) {
const item = symbols.find(s => s.symbol === value);
if (!item) return;
document.getElementById('f-usdt').value = item.min_notional;
document.getElementById('f-min-label').textContent = `(min ${item.min_notional} USDT)`;
},
});
}
async function loadBots() {
const res = await api('GET', '/bots');
const bots = await res.json();
document.getElementById('bot-count').textContent = bots.length;
const tbody = document.getElementById('bots-body');
if (!bots.length) { tbody.innerHTML = '<tr><td colspan="8" class="empty">Henüz bot yok</td></tr>'; return; }
tbody.innerHTML = bots.map(b => `
<tr>
<td>${esc(b.name)}</td>
<td><strong>${esc(b.symbol)}</strong></td>
<td>${esc(b.timeframe)}</td>
<td>${b.usdt_amount} USDT</td>
<td>%${b.profit_percent}</td>
<td>${b.testnet ? '🟡 Test' : '🟢 Gerçek'}</td>
<td><span class="badge ${b.running ? 'badge-running' : 'badge-stopped'}">${b.running ? 'Çalışıyor' : 'Durdu'}</span></td>
<td style="display:flex;gap:6px;flex-wrap:wrap">
${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>
</td>
</tr>`).join('');
}
async function loadPositions() {
const res = await api('GET', '/positions');
const positions = await res.json();
const tbody = document.getElementById('positions-body');
if (!positions.length) { tbody.innerHTML = '<tr><td colspan="6" class="empty">Açık pozisyon yok</td></tr>'; return; }
tbody.innerHTML = positions.map(p => `
<tr>
<td>${esc(p.bot_name)}</td>
<td><strong>${esc(p.symbol)}</strong></td>
<td>${p.buy_price.toFixed(6)}</td>
<td>${p.sell_target.toFixed(6)}</td>
<td>${p.quantity}</td>
<td>${new Date(p.opened_at).toLocaleString('tr-TR')}</td>
</tr>`).join('');
}
async function loadClosed() {
const res = await api('GET', '/positions/closed');
const closed = await res.json();
const tbody = document.getElementById('closed-body');
if (!closed.length) { tbody.innerHTML = '<tr><td colspan="7" class="empty">Henüz işlem yok</td></tr>'; return; }
tbody.innerHTML = closed.map(c => `
<tr>
<td>${esc(c.bot_name)}</td>
<td><strong>${esc(c.symbol)}</strong></td>
<td>${c.buy_price.toFixed(6)}</td>
<td>${c.sell_target.toFixed(6)}</td>
<td>${c.quantity}</td>
<td><span class="badge ${c.status==='FILLED'?'badge-running':'badge-stopped'}">${c.status}</span></td>
<td>${new Date(c.closed_at).toLocaleString('tr-TR')}</td>
</tr>`).join('');
}
async function createBot() {
const name = document.getElementById('f-name').value.trim();
const symbol = document.getElementById('f-symbol').value.trim().toUpperCase();
const timeframe = document.getElementById('f-timeframe').value;
const usdt_amount = parseFloat(document.getElementById('f-usdt').value);
const profit_percent = parseFloat(document.getElementById('f-profit').value);
const testnet = document.getElementById('f-testnet').value === 'true';
if (!name || !symbol || !usdt_amount || !profit_percent) { alert('Tüm alanları doldurun'); return; }
await api('POST', '/bots', { name, symbol, timeframe, usdt_amount, profit_percent, testnet });
loadBots();
}
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();
}
function connectSSE() {
if (sseSource) sseSource.close();
sseSource = new EventSource('/api/events', {
headers: { 'Authorization': 'Bearer ' + AUTH_TOKEN }
});
// EventSource doesn't support custom headers natively; use URL param workaround
sseSource.close();
sseSource = new EventSource(`/api/events?token=${encodeURIComponent(AUTH_TOKEN)}`);
sseSource.addEventListener('trade', () => {
loadPositions();
loadClosed();
});
sseSource.onopen = () => {
document.getElementById('sse-dot').className = 'status-dot connected';
document.getElementById('sse-label').textContent = 'canlı';
};
sseSource.onerror = () => {
document.getElementById('sse-dot').className = 'status-dot';
document.getElementById('sse-label').textContent = 'bağlantı kesildi';
setTimeout(connectSSE, 5000);
};
}
function esc(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
if (AUTH_TOKEN) tryConnect();
else showAuthOverlay();
</script>
</body>
</html>