feat: modal ile bot ekleme, TomSelect minChars 0
This commit is contained in:
parent
4104d82ef0
commit
ef4aabafb5
1 changed files with 109 additions and 61 deletions
|
|
@ -45,26 +45,41 @@
|
||||||
.btn-delete:hover { background: rgba(231,76,60,0.1); }
|
.btn-delete:hover { background: rgba(231,76,60,0.1); }
|
||||||
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||||
.btn-primary:hover { background: #5a52d5; }
|
.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; }
|
.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 { 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 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 { 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-box input:focus { outline: none; border-color: var(--accent); }
|
||||||
.auth-error { color: var(--red); font-size: 12px; display: none; }
|
.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-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 { padding: 7px 12px; }
|
||||||
.ts-dropdown .option.active { background: rgba(108,99,255,0.2); color: var(--text); }
|
.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-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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -72,12 +87,66 @@
|
||||||
<div id="auth-overlay">
|
<div id="auth-overlay">
|
||||||
<div class="auth-box">
|
<div class="auth-box">
|
||||||
<h2>Mukan Special Edition</h2>
|
<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>
|
<button class="btn btn-primary" onclick="login()">Giriş</button>
|
||||||
<span class="auth-error" id="auth-error">Geçersiz token</span>
|
<span class="auth-error" id="auth-error">Geçersiz token</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<header>
|
||||||
<h1>MSE Dashboard</h1>
|
<h1>MSE Dashboard</h1>
|
||||||
<span class="status-dot" id="sse-dot"></span>
|
<span class="status-dot" id="sse-dot"></span>
|
||||||
|
|
@ -86,47 +155,16 @@
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<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">
|
<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>
|
<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>
|
<tbody id="bots-body"><tr><td colspan="8" class="empty">Yükleniyor...</td></tr></tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</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>
|
<section>
|
||||||
<div class="section-header">Açık Pozisyonlar</div>
|
<div class="section-header">Açık Pozisyonlar</div>
|
||||||
<table>
|
<table>
|
||||||
|
|
@ -148,6 +186,7 @@
|
||||||
let AUTH_TOKEN = localStorage.getItem('mse_token') || '';
|
let AUTH_TOKEN = localStorage.getItem('mse_token') || '';
|
||||||
let sseSource = null;
|
let sseSource = null;
|
||||||
let symbolSelect = null;
|
let symbolSelect = null;
|
||||||
|
let allSymbols = [];
|
||||||
|
|
||||||
function login() {
|
function login() {
|
||||||
const t = document.getElementById('token-input').value.trim();
|
const t = document.getElementById('token-input').value.trim();
|
||||||
|
|
@ -191,14 +230,15 @@ async function loadAll() {
|
||||||
|
|
||||||
async function loadSymbols() {
|
async function loadSymbols() {
|
||||||
const res = await api('GET', '/symbols');
|
const res = await api('GET', '/symbols');
|
||||||
const symbols = await res.json();
|
allSymbols = await res.json();
|
||||||
|
|
||||||
symbolSelect = new TomSelect('#f-symbol', {
|
symbolSelect = new TomSelect('#f-symbol', {
|
||||||
valueField: 'symbol',
|
valueField: 'symbol',
|
||||||
labelField: 'symbol',
|
labelField: 'symbol',
|
||||||
searchField: 'symbol',
|
searchField: 'symbol',
|
||||||
options: symbols,
|
options: allSymbols,
|
||||||
placeholder: 'Ara: DOGE, BTC...',
|
placeholder: 'Ara veya seç...',
|
||||||
|
minChars: 0,
|
||||||
render: {
|
render: {
|
||||||
option: (data) =>
|
option: (data) =>
|
||||||
`<div style="display:flex;justify-content:space-between;align-items:center">
|
`<div style="display:flex;justify-content:space-between;align-items:center">
|
||||||
|
|
@ -208,7 +248,7 @@ async function loadSymbols() {
|
||||||
item: (data) => `<div>${data.symbol}</div>`,
|
item: (data) => `<div>${data.symbol}</div>`,
|
||||||
},
|
},
|
||||||
onChange(value) {
|
onChange(value) {
|
||||||
const item = symbols.find(s => s.symbol === value);
|
const item = allSymbols.find(s => s.symbol === value);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
document.getElementById('f-usdt').value = item.min_notional;
|
document.getElementById('f-usdt').value = item.min_notional;
|
||||||
document.getElementById('f-min-label').textContent = `(min ${item.min_notional} USDT)`;
|
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() {
|
async function loadBots() {
|
||||||
const res = await api('GET', '/bots');
|
const res = await api('GET', '/bots');
|
||||||
const bots = await res.json();
|
const bots = await res.json();
|
||||||
|
|
@ -276,13 +327,19 @@ async function loadClosed() {
|
||||||
|
|
||||||
async function createBot() {
|
async function createBot() {
|
||||||
const name = document.getElementById('f-name').value.trim();
|
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 timeframe = document.getElementById('f-timeframe').value;
|
||||||
const usdt_amount = parseFloat(document.getElementById('f-usdt').value);
|
const usdt_amount = parseFloat(document.getElementById('f-usdt').value);
|
||||||
const profit_percent = parseFloat(document.getElementById('f-profit').value);
|
const profit_percent = parseFloat(document.getElementById('f-profit').value);
|
||||||
const testnet = document.getElementById('f-testnet').value === 'true';
|
const testnet = document.getElementById('f-testnet').value === 'true';
|
||||||
if (!name || !symbol || !usdt_amount || !profit_percent) { alert('Tüm alanları doldurun'); return; }
|
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 });
|
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();
|
loadBots();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,17 +361,8 @@ async function deleteBot(id, name) {
|
||||||
|
|
||||||
function connectSSE() {
|
function connectSSE() {
|
||||||
if (sseSource) sseSource.close();
|
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 = new EventSource(`/api/events?token=${encodeURIComponent(AUTH_TOKEN)}`);
|
||||||
|
sseSource.addEventListener('trade', () => { loadPositions(); loadClosed(); });
|
||||||
sseSource.addEventListener('trade', () => {
|
|
||||||
loadPositions();
|
|
||||||
loadClosed();
|
|
||||||
});
|
|
||||||
sseSource.onopen = () => {
|
sseSource.onopen = () => {
|
||||||
document.getElementById('sse-dot').className = 'status-dot connected';
|
document.getElementById('sse-dot').className = 'status-dot connected';
|
||||||
document.getElementById('sse-label').textContent = 'canlı';
|
document.getElementById('sse-label').textContent = 'canlı';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue