feat: modal ile bot ekleme, TomSelect minChars 0

This commit is contained in:
Mukan Erkin TÖRÜK 2026-04-19 06:50:00 +03:00
parent 4104d82ef0
commit ef4aabafb5

View file

@ -45,26 +45,41 @@
.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 overlay */
#auth-overlay { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 200; }
.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; }
/* Modal */
#modal-overlay { position: fixed; inset: 0; background: rgba(0,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; display: flex; flex-direction: column; }
.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; line-height: 1; padding: 0 4px; }
.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: 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-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* TomSelect dark theme */
.ts-wrapper .ts-control { background: var(--bg); border-color: var(--border); color: var(--text); min-height: 34px; 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 { background: var(--surface); border-color: var(--border); color: var(--text); z-index: 150; }
.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; }
.ts-control input { color: var(--text) !important; }
</style>
</head>
<body>
@ -72,12 +87,66 @@
<div id="auth-overlay">
<div class="auth-box">
<h2>Mukan Special Edition</h2>
<input type="password" id="token-input" placeholder="Auth token" />
<input type="password" id="token-input" placeholder="Auth token" onkeydown="if(event.key==='Enter')login()" />
<button class="btn btn-primary" onclick="login()">Giriş</button>
<span class="auth-error" id="auth-error">Geçersiz token</span>
</div>
</div>
<div id="modal-overlay" onclick="if(event.target===this)closeModal()">
<div class="modal">
<div class="modal-header">
<h2>Yeni Bot Ekle</h2>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<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-row">
<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>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-row">
<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="—" />
</div>
<div class="form-group">
<label>Kar %</label>
<input id="f-profit" type="number" step="0.1" placeholder="2" />
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn" onclick="closeModal()">İptal</button>
<button class="btn btn-primary" onclick="createBot()">Bot Ekle</button>
</div>
</div>
</div>
<header>
<h1>MSE Dashboard</h1>
<span class="status-dot" id="sse-dot"></span>
@ -86,47 +155,16 @@
<main>
<section>
<div class="section-header">Botlar <span id="bot-count" style="color:var(--text)">0</span></div>
<div class="section-header">
Botlar <span id="bot-count" style="color:var(--text)">0</span>
<button class="btn btn-primary" onclick="openModal()" style="font-size:12px;padding:4px 12px">+ Yeni Bot</button>
</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>
@ -148,6 +186,7 @@
let AUTH_TOKEN = localStorage.getItem('mse_token') || '';
let sseSource = null;
let symbolSelect = null;
let allSymbols = [];
function login() {
const t = document.getElementById('token-input').value.trim();
@ -191,14 +230,15 @@ async function loadAll() {
async function loadSymbols() {
const res = await api('GET', '/symbols');
const symbols = await res.json();
allSymbols = await res.json();
symbolSelect = new TomSelect('#f-symbol', {
valueField: 'symbol',
labelField: 'symbol',
searchField: 'symbol',
options: symbols,
placeholder: 'Ara: DOGE, BTC...',
options: allSymbols,
placeholder: 'Ara veya seç...',
minChars: 0,
render: {
option: (data) =>
`<div style="display:flex;justify-content:space-between;align-items:center">
@ -208,7 +248,7 @@ async function loadSymbols() {
item: (data) => `<div>${data.symbol}</div>`,
},
onChange(value) {
const item = symbols.find(s => s.symbol === value);
const item = allSymbols.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)`;
@ -216,6 +256,17 @@ async function loadSymbols() {
});
}
function openModal() {
document.getElementById('modal-overlay').classList.add('open');
if (symbolSelect) symbolSelect.focus();
}
function closeModal() {
document.getElementById('modal-overlay').classList.remove('open');
}
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
async function loadBots() {
const res = await api('GET', '/bots');
const bots = await res.json();
@ -276,13 +327,19 @@ async function loadClosed() {
async function createBot() {
const name = document.getElementById('f-name').value.trim();
const symbol = document.getElementById('f-symbol').value.trim().toUpperCase();
const symbol = symbolSelect ? symbolSelect.getValue() : '';
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 });
closeModal();
document.getElementById('f-name').value = '';
if (symbolSelect) symbolSelect.clear();
document.getElementById('f-usdt').value = '';
document.getElementById('f-profit').value = '';
document.getElementById('f-min-label').textContent = '';
loadBots();
}
@ -304,17 +361,8 @@ async function deleteBot(id, name) {
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.addEventListener('trade', () => { loadPositions(); loadClosed(); });
sseSource.onopen = () => {
document.getElementById('sse-dot').className = 'status-dot connected';
document.getElementById('sse-label').textContent = 'canlı';