feat: Enhance dashboard with live overview panel and control features
This commit is contained in:
@@ -68,19 +68,62 @@
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.toolbar form {
|
||||
margin: 0;
|
||||
}
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
background: #2d6cdf;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
}
|
||||
.button.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
.button.danger {
|
||||
background: #ba3d4f;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
color: #9fb2d0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.field input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: #e5eefb;
|
||||
font: inherit;
|
||||
}
|
||||
.field.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.field.checkbox input {
|
||||
width: auto;
|
||||
}
|
||||
.control-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -113,6 +156,26 @@
|
||||
{% include "partials/metrics.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="overview-shell"
|
||||
hx-get="{{ overview_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/overview.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="controls-shell"
|
||||
hx-get="{{ controls_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 20s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/controls.html" %}
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const stream = new EventSource("{{ stream_endpoint }}");
|
||||
stream.addEventListener("metrics", (event) => {
|
||||
@@ -121,6 +184,15 @@
|
||||
panel.outerHTML = JSON.parse(event.data);
|
||||
}
|
||||
});
|
||||
const overviewStream = new EventSource(
|
||||
"{{ overview_stream_endpoint }}",
|
||||
);
|
||||
overviewStream.addEventListener("overview", (event) => {
|
||||
const panel = document.getElementById("overview-panel");
|
||||
if (panel) {
|
||||
panel.outerHTML = JSON.parse(event.data);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<div id="controls-panel" class="panel" style="margin-top: 16px">
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<div class="label">Runtime Status</div>
|
||||
<div class="value">{{ execution_status }}</div>
|
||||
<div class="meta">Updated {{ updated_at }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Kill Switch</div>
|
||||
<div class="value">{{ kill_switch_status }}</div>
|
||||
<div class="meta">Reason {{ kill_switch_reason }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Config Snapshot</div>
|
||||
<div class="meta">Paper trading: {{ paper_trading_mode }}</div>
|
||||
<div class="meta">Trade capital: {{ trade_capital_usd }}</div>
|
||||
<div class="meta">Max trade capital: {{ max_trade_capital_usd }}</div>
|
||||
<div class="meta">Max concurrent trades: {{ max_concurrent_trades }}</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid"
|
||||
style="
|
||||
margin-top: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
"
|
||||
>
|
||||
<article class="card">
|
||||
<div class="label">Execution Controls</div>
|
||||
<div class="control-actions">
|
||||
<form
|
||||
hx-post="{{ start_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit" class="button">Start</button>
|
||||
</form>
|
||||
<form
|
||||
hx-post="{{ stop_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit" class="button secondary">Stop</button>
|
||||
</form>
|
||||
<form
|
||||
hx-post="{{ kill_switch_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<input type="hidden" name="reason" value="manual" />
|
||||
<button type="submit" class="button danger">
|
||||
Trigger Kill Switch
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Edit Config</div>
|
||||
<form
|
||||
class="form-grid"
|
||||
hx-post="{{ config_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label class="field">
|
||||
<span>Trade capital USD</span>
|
||||
<input
|
||||
name="trade_capital_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ trade_capital_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max trade capital USD</span>
|
||||
<input
|
||||
name="max_trade_capital_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ max_trade_capital_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max concurrent trades</span>
|
||||
<input
|
||||
name="max_concurrent_trades"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ max_concurrent_trades_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
{% set check = "checked" if paper_trading_mode == "enabled" else "" %}
|
||||
<input name="paper_trading_mode" type="checkbox" {{ check }} />
|
||||
<span>Paper trading mode</span>
|
||||
</label>
|
||||
<button type="submit" class="button">Save config</button>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,67 @@
|
||||
<div id="overview-panel" class="panel" style="margin-top: 16px">
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<div class="label">Status</div>
|
||||
<div class="value">{{ status }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Balances</div>
|
||||
<div class="value">{{ balances }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Open Trades</div>
|
||||
<div class="value">{{ open_trade_count }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Realized P&L</div>
|
||||
<div class="value">{{ realized_pnl_total }}</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid"
|
||||
style="
|
||||
margin-top: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
"
|
||||
>
|
||||
<article class="card">
|
||||
<div class="label">Open Trades</div>
|
||||
<ul>
|
||||
{% for trade in open_trades %}
|
||||
<li>
|
||||
{{ trade.trade_ref }} - {{ trade.status }} - {{ trade.cycle }} - {{
|
||||
trade.started_at }}
|
||||
</li>
|
||||
{% else %}
|
||||
<li>No open trades.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Balances Snapshot</div>
|
||||
<div
|
||||
class="value"
|
||||
style="font-size: 1rem; font-weight: 500; word-break: break-word"
|
||||
>
|
||||
{{ balances }}
|
||||
</div>
|
||||
<div class="meta">Total value {{ total_value }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Opportunity Feed</div>
|
||||
<ul>
|
||||
{% for opp in opportunities %}
|
||||
<li>
|
||||
{{ opp.cycle }} - {{ opp.net_pct }} - {{ opp.est_profit }} - {{
|
||||
opp.detected_at }}
|
||||
</li>
|
||||
{% else %}
|
||||
<li>No opportunities.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="meta">Updated {{ generated_at }}</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user