From 64d02e931a56096b0e32957399a3c7004cfb210e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?=
<78488229+viktor138irk@users.noreply.github.com>
Date: Fri, 8 May 2026 03:59:58 +0900
Subject: [PATCH] Render dashboard settings and AI result as human-readable UI
---
app/static/index.html | 69 +++++++++++++++++++++++++++++++++----------
1 file changed, 53 insertions(+), 16 deletions(-)
diff --git a/app/static/index.html b/app/static/index.html
index 96cb687..96f879c 100644
--- a/app/static/index.html
+++ b/app/static/index.html
@@ -29,7 +29,12 @@
.good { border-color: #166534; color: #bbf7d0; }
.bad { border-color: #7f1d1d; color: #fecaca; }
.wide { width: 100%; }
- pre { white-space: pre-wrap; word-break: break-word; font-size: 12px; color: #c2cef7; max-height: 220px; overflow: auto; background:#0b1228; padding: 12px; border-radius: 12px; border: 1px solid #202e56; }
+ .box { background:#0b1228; padding: 12px; border-radius: 12px; border: 1px solid #202e56; margin: 8px 0 14px; }
+ .line { display:flex; justify-content:space-between; gap:12px; padding: 7px 0; border-bottom:1px solid rgba(145,162,214,.12); }
+ .line:last-child { border-bottom:0; }
+ .line b { color:#fff; }
+ .text { color:#c2cef7; font-size: 13px; line-height: 1.45; }
+ pre { white-space: pre-wrap; word-break: break-word; font-size: 12px; color: #c2cef7; max-height: 160px; overflow: auto; background:#0b1228; padding: 12px; border-radius: 12px; border: 1px solid #202e56; }
.headline { font-size: 15px; font-weight: 800; margin-bottom: 8px; }
.muted { color: var(--muted); font-size: 13px; }
@media (max-width: 1080px) { main { grid-template-columns: 1fr; } .cards { grid-template-columns: 1fr; } }
@@ -78,13 +83,15 @@
-
AI-результат появится здесь.
+ AI-результат появится здесь.
Настройки
- —
+ —
- Последнее событие
- —
+
+ Техническое событие
+ —
+
@@ -103,7 +110,42 @@ let currentCandle = null;
let appState = null;
function getMarket() { return document.getElementById('marketSelect').value || 'BTCUSDT'; }
-function setJson(id, obj) { document.getElementById(id).textContent = JSON.stringify(obj, null, 2); }
+function actionRu(action) { return { buy: 'Покупать', sell: 'Продавать', hold: 'Ждать' }[action] || action || '—'; }
+function boolRu(value) { return value ? 'включен' : 'выключен'; }
+function htmlEscape(s) { return String(s ?? '').replace(/[&<>'"]/g, c => ({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); }
+
+function renderDecision(obj) {
+ const d = obj.decision || obj;
+ const indicators = d.indicators || obj.indicators || {};
+ const executed = obj.executed === true ? 'Да' : obj.executed === false ? 'Нет' : '—';
+ const mode = obj.mode ? obj.mode.toUpperCase() : '—';
+ const reason = d.reason || obj.reason || 'Нет причины.';
+ document.getElementById('aiResult').innerHTML = `
+ Пара${htmlEscape(d.market || obj.market || '—')}
+ Действие${htmlEscape(actionRu(d.action || obj.action))}
+ Оценка AI${Number(d.score ?? obj.score ?? 0).toFixed(1)} / 100
+ Уверенность${Number(d.confidence ?? obj.confidence ?? 0).toFixed(1)}%
+ Цена${Number(indicators.last_price ?? obj.last_price ?? 0).toLocaleString('ru-RU')}
+ Режим${htmlEscape(mode)}
+ Сделка выполнена${executed}
+ Причина:
${htmlEscape(reason)}
+ `;
+}
+
+function renderSettings(settings) {
+ if (!settings) return;
+ const mode = settings.trade_mode || 'demo';
+ document.getElementById('modeLabel').textContent = mode.toUpperCase();
+ document.getElementById('modeLabel').style.color = mode === 'live' ? 'var(--bad)' : 'var(--good)';
+ document.getElementById('botSettings').innerHTML = `
+ Бот${boolRu(settings.enabled)}
+ Счет${mode === 'live' ? 'Live' : 'Demo'}
+ Стратегия${htmlEscape(settings.trade_style_mode || 'balanced')}
+ Сумма сделки${Number(settings.max_quote_per_trade || 0).toFixed(2)} USDT
+ Мин. оценка AI${Number(settings.min_signal_score || 0).toFixed(0)} / 100
+ Макс. позиций${settings.max_open_positions ?? '—'}
+ `;
+}
async function bootstrap() {
const res = await fetch('/api/v1/app/bootstrap');
@@ -163,27 +205,22 @@ async function autoPickMarket() {
const json = await res.json();
if (json.market) {
document.getElementById('marketSelect').value = json.market;
- setJson('aiResult', json);
+ renderDecision(json);
await loadSelectedMarket();
- } else setJson('aiResult', json);
+ } else renderDecision(json);
}
function renderAccount(acc) {
if (!acc) return;
document.getElementById('balance').textContent = `${Number(acc.balance).toFixed(2)} ${acc.quote_asset}`;
}
-function renderSettings(settings) {
- if (!settings) return;
- document.getElementById('modeLabel').textContent = (settings.trade_mode || 'demo').toUpperCase();
- setJson('botSettings', settings);
-}
async function loadAccount() { const res = await fetch('/api/v1/demo/account'); renderAccount(await res.json()); }
async function loadBotSettings() { const res = await fetch('/api/v1/bot/settings'); renderSettings(await res.json()); }
-async function analyze() { const res = await fetch(`/api/v1/bot/analyze?market=${getMarket()}`); setJson('aiResult', await res.json()); }
-async function decide() { const res = await fetch(`/api/v1/bot/decide?market=${getMarket()}`, { method: 'POST' }); setJson('aiResult', await res.json()); }
+async function analyze() { const res = await fetch(`/api/v1/bot/analyze?market=${getMarket()}`); renderDecision(await res.json()); }
+async function decide() { const res = await fetch(`/api/v1/bot/decide?market=${getMarket()}`, { method: 'POST' }); renderDecision(await res.json()); }
async function autoTrade() {
const res = await fetch(`/api/v1/bot/auto-trade?market=${getMarket()}`, { method: 'POST' });
- setJson('aiResult', await res.json());
+ renderDecision(await res.json());
await loadAccount();
await loadMarkers(getMarket());
}