Fix frontend disconnected, FIX UI reloader, add dashboard clock
Browse filesFrontend (disconnected root cause):
- All fetch('/endpoint') calls were hitting nginx '/' (dashboard) instead
of '/frontend/endpoint'. Added BASE detection at page load:
const BASE = window.location.pathname === '/' ? '' : pathname.replace(/\/$/, '');
Applied to /securities, /order, /book, /trades
FIX UI (session disrupted by reloader):
- debug=True caused Flask reloader to spawn a child process and restart
the app, breaking the QuickFIX session on startup
- Changed to debug=False, use_reloader=False
Entrypoint timing:
- FIX OEG sleep increased from 3s β 6s (allow full socket bind)
- FIX UI sleep increased from 2s β 3s (allow QuickFIX initiator to start)
Dashboard:
- Local clock added to header bar (updates every second, browser timezone)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dashboard/templates/index.html +11 -0
- entrypoint.sh +2 -2
- fix-ui-client/fix-ui-client.py +1 -1
- frontend/templates/index.html +7 -4
|
@@ -201,6 +201,7 @@
|
|
| 201 |
Trading Dashboard
|
| 202 |
<span id="status" class="status connecting"><span class="dot"></span><span id="status-text">Connecting...</span></span>
|
| 203 |
<span id="session-badge" class="status idle"><span class="dot"></span><span id="session-text">IDLE</span></span>
|
|
|
|
| 204 |
<button id="day-btn" onclick="toggleDay()" class="btn-day btn-start" disabled>Start of Day</button>
|
| 205 |
<button id="suspend-btn" onclick="toggleSuspend()" class="btn-day btn-suspend" disabled>Suspend</button>
|
| 206 |
<button id="mode-btn" onclick="toggleMode()" class="btn-day btn-automatic">Automatic</button>
|
|
@@ -1383,6 +1384,16 @@
|
|
| 1383 |
}
|
| 1384 |
|
| 1385 |
// Initial load: fetch data via REST, then connect SSE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1386 |
async function init() {
|
| 1387 |
await fetchData();
|
| 1388 |
connectSSE();
|
|
|
|
| 201 |
Trading Dashboard
|
| 202 |
<span id="status" class="status connecting"><span class="dot"></span><span id="status-text">Connecting...</span></span>
|
| 203 |
<span id="session-badge" class="status idle"><span class="dot"></span><span id="session-text">IDLE</span></span>
|
| 204 |
+
<span id="local-clock" style="font-size:13px; font-weight:normal; color:#555; background:#f0f0f0; padding:3px 10px; border-radius:12px; font-variant-numeric:tabular-nums;"></span>
|
| 205 |
<button id="day-btn" onclick="toggleDay()" class="btn-day btn-start" disabled>Start of Day</button>
|
| 206 |
<button id="suspend-btn" onclick="toggleSuspend()" class="btn-day btn-suspend" disabled>Suspend</button>
|
| 207 |
<button id="mode-btn" onclick="toggleMode()" class="btn-day btn-automatic">Automatic</button>
|
|
|
|
| 1384 |
}
|
| 1385 |
|
| 1386 |
// Initial load: fetch data via REST, then connect SSE
|
| 1387 |
+
// ββ Local clock βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1388 |
+
(function tickClock() {
|
| 1389 |
+
const el = document.getElementById("local-clock");
|
| 1390 |
+
if (el) {
|
| 1391 |
+
const now = new Date();
|
| 1392 |
+
el.textContent = now.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
| 1393 |
+
}
|
| 1394 |
+
setTimeout(tickClock, 1000);
|
| 1395 |
+
})();
|
| 1396 |
+
|
| 1397 |
async function init() {
|
| 1398 |
await fetchData();
|
| 1399 |
connectSSE();
|
|
@@ -59,11 +59,11 @@ python3 /app/mdf_simulator.py &
|
|
| 59 |
|
| 60 |
echo "[startup] Starting FIX OEG on port 5001..."
|
| 61 |
(cd /app/fix_oeg && python3 /app/fix_oeg/fix_oeg_server.py) &
|
| 62 |
-
sleep
|
| 63 |
|
| 64 |
echo "[startup] Starting FIX UI Client on port 5002..."
|
| 65 |
python3 /app/fix_ui/fix_ui_client.py &
|
| 66 |
-
sleep
|
| 67 |
|
| 68 |
echo "[startup] Starting Frontend on port 5003..."
|
| 69 |
PORT=$FRONTEND_PORT TEMPLATE_FOLDER=/app/frontend_templates python3 /app/frontend.py &
|
|
|
|
| 59 |
|
| 60 |
echo "[startup] Starting FIX OEG on port 5001..."
|
| 61 |
(cd /app/fix_oeg && python3 /app/fix_oeg/fix_oeg_server.py) &
|
| 62 |
+
sleep 6
|
| 63 |
|
| 64 |
echo "[startup] Starting FIX UI Client on port 5002..."
|
| 65 |
python3 /app/fix_ui/fix_ui_client.py &
|
| 66 |
+
sleep 3
|
| 67 |
|
| 68 |
echo "[startup] Starting Frontend on port 5003..."
|
| 69 |
PORT=$FRONTEND_PORT TEMPLATE_FOLDER=/app/frontend_templates python3 /app/frontend.py &
|
|
@@ -197,4 +197,4 @@ CONFIG_FILE = os.getenv("FIX_CONFIG", "client.cfg")
|
|
| 197 |
PORT = int(os.getenv("UI_PORT", "5002"))
|
| 198 |
|
| 199 |
if __name__ == "__main__":
|
| 200 |
-
app.run(host="0.0.0.0", port=PORT, debug=
|
|
|
|
| 197 |
PORT = int(os.getenv("UI_PORT", "5002"))
|
| 198 |
|
| 199 |
if __name__ == "__main__":
|
| 200 |
+
app.run(host="0.0.0.0", port=PORT, debug=False, use_reloader=False)
|
|
@@ -211,6 +211,9 @@
|
|
| 211 |
</div>
|
| 212 |
|
| 213 |
<script>
|
|
|
|
|
|
|
|
|
|
| 214 |
let pollInterval = null;
|
| 215 |
|
| 216 |
function setStatus(cls, text) {
|
|
@@ -223,7 +226,7 @@
|
|
| 223 |
|
| 224 |
async function loadSecurities() {
|
| 225 |
try {
|
| 226 |
-
const r = await fetch('/securities');
|
| 227 |
const symbols = await r.json();
|
| 228 |
const sel = document.getElementById('symbol-select');
|
| 229 |
sel.innerHTML = '';
|
|
@@ -251,7 +254,7 @@
|
|
| 251 |
timestamp: Date.now() / 1000
|
| 252 |
};
|
| 253 |
try {
|
| 254 |
-
const r = await fetch('/order', {
|
| 255 |
method: 'POST',
|
| 256 |
headers: {'Content-Type': 'application/json'},
|
| 257 |
body: JSON.stringify(data)
|
|
@@ -324,7 +327,7 @@
|
|
| 324 |
async function loadBook() {
|
| 325 |
const now = new Date().toLocaleTimeString();
|
| 326 |
try {
|
| 327 |
-
const r = await fetch('/book');
|
| 328 |
const b = await r.json();
|
| 329 |
renderBook(b);
|
| 330 |
document.getElementById('book-updated').textContent = 'Updated: ' + now;
|
|
@@ -335,7 +338,7 @@
|
|
| 335 |
setStatus('disconnected', 'Disconnected');
|
| 336 |
}
|
| 337 |
try {
|
| 338 |
-
const r2 = await fetch('/trades');
|
| 339 |
const t = await r2.json();
|
| 340 |
renderTrades(Array.isArray(t) ? t : (t.trades || []));
|
| 341 |
document.getElementById('trades-updated').textContent = 'Updated: ' + now;
|
|
|
|
| 211 |
</div>
|
| 212 |
|
| 213 |
<script>
|
| 214 |
+
// Detect API base path: works whether served at / or under /frontend/
|
| 215 |
+
const BASE = window.location.pathname === '/' ? '' : window.location.pathname.replace(/\/$/, '');
|
| 216 |
+
|
| 217 |
let pollInterval = null;
|
| 218 |
|
| 219 |
function setStatus(cls, text) {
|
|
|
|
| 226 |
|
| 227 |
async function loadSecurities() {
|
| 228 |
try {
|
| 229 |
+
const r = await fetch(BASE + '/securities');
|
| 230 |
const symbols = await r.json();
|
| 231 |
const sel = document.getElementById('symbol-select');
|
| 232 |
sel.innerHTML = '';
|
|
|
|
| 254 |
timestamp: Date.now() / 1000
|
| 255 |
};
|
| 256 |
try {
|
| 257 |
+
const r = await fetch(BASE + '/order', {
|
| 258 |
method: 'POST',
|
| 259 |
headers: {'Content-Type': 'application/json'},
|
| 260 |
body: JSON.stringify(data)
|
|
|
|
| 327 |
async function loadBook() {
|
| 328 |
const now = new Date().toLocaleTimeString();
|
| 329 |
try {
|
| 330 |
+
const r = await fetch(BASE + '/book');
|
| 331 |
const b = await r.json();
|
| 332 |
renderBook(b);
|
| 333 |
document.getElementById('book-updated').textContent = 'Updated: ' + now;
|
|
|
|
| 338 |
setStatus('disconnected', 'Disconnected');
|
| 339 |
}
|
| 340 |
try {
|
| 341 |
+
const r2 = await fetch(BASE + '/trades');
|
| 342 |
const t = await r2.json();
|
| 343 |
renderTrades(Array.isArray(t) ? t : (t.trades || []));
|
| 344 |
document.getElementById('trades-updated').textContent = 'Updated: ' + now;
|