RayMelius's picture
Add new securities and use real prices in FIX UI order entry
9136daa
Raw
History Blame Contribute Delete
8.89 kB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>FIX UI Client</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f7f7f7; }
h1 { display: flex; align-items: center; gap: 15px; margin-bottom: 20px; }
h2 { margin: 5px 0 10px; font-size: 16px; }
.container { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px; }
.panel {
background: #fff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
min-height: 400px;
}
/* Status badge */
.status {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
}
.status .dot { width: 10px; height: 10px; border-radius: 50%; }
.status.connected { background: #d4edda; color: #155724; }
.status.connected .dot { background: #28a745; }
.status.disconnected { background: #f8d7da; color: #721c24; }
.status.disconnected .dot { background: #dc3545; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.status.connecting { background: #fff3cd; color: #856404; }
.status.connecting .dot { background: #ffc107; animation: pulse 1s infinite; }
/* Buttons */
.btn-group { display: flex; gap: 8px; margin-top: 10px; }
.btn {
padding: 7px 18px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
font-weight: bold;
}
.btn-connect { background: #28a745; color: #fff; }
.btn-connect:hover { background: #218838; }
.btn-disconnect { background: #dc3545; color: #fff; }
.btn-disconnect:hover { background: #c82333; }
/* Divider */
.divider { border: none; border-top: 1px solid #eee; margin: 15px 0; }
/* Order form */
.form-group { margin-bottom: 12px; }
.form-group label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; }
.form-group input,
.form-group select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 13px;
box-sizing: border-box;
}
.form-group select option[value="buy"] { color: #2e7d32; }
.form-group select option[value="sell"] { color: #c62828; }
.btn-send {
width: 100%;
padding: 10px;
background: #2196F3;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
.btn-send:hover { background: #1976D2; }
/* Order feedback */
#order-status {
padding: 8px 12px;
border-radius: 4px;
font-size: 13px;
margin-top: 10px;
display: none;
}
#order-status.success { background: #d4edda; color: #155724; display: block; }
#order-status.error { background: #f8d7da; color: #721c24; display: block; }
/* Messages box */
.messages-box {
flex-grow: 1;
overflow-y: auto;
background: #1a1a2e;
color: #e0e0e0;
padding: 12px;
border-radius: 6px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
line-height: 1.5;
min-height: 300px;
}
/* Mobile responsive */
@media (max-width: 768px) {
body { margin: 10px; }
h1 { font-size: 18px; flex-wrap: wrap; gap: 8px; }
.container { grid-template-columns: 1fr; }
.panel { min-height: unset; }
}
@media (max-width: 480px) {
body { margin: 6px; }
h1 { font-size: 14px; }
.btn { font-size: 12px; padding: 6px 12px; }
}
</style>
</head>
<body>
<h1>
FIX UI Client
<span id="status-badge" class="status connecting">
<span class="dot"></span>
<span id="status-text">Connecting...</span>
</span>
<a onclick="window.location.href='/'" style="margin-left:auto; padding:4px 14px; background:#6c757d; color:#fff; border-radius:20px; font-size:12px; font-weight:bold; text-decoration:none; cursor:pointer;">← Dashboard</a>
</h1>
<div class="container">
<!-- Left panel: Connection + Order form -->
<div class="panel">
<h2>Connection</h2>
<div class="btn-group">
<button onclick="doConnect()" class="btn btn-connect">Connect</button>
<button onclick="doDisconnect()" class="btn btn-disconnect">Disconnect</button>
</div>
<hr class="divider">
<h2>Send Order</h2>
<form id="orderForm" onsubmit="sendOrder(event)">
<div class="form-group">
<label>Side</label>
<select id="f-side">
<option value="buy">BUY</option>
<option value="sell">SELL</option>
</select>
</div>
<div class="form-group">
<label>Symbol</label>
<select id="f-symbol" onchange="updatePrice()">
{% for s in securities %}
<option value="{{ s }}">{{ s }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>Quantity</label>
<input id="f-qty" type="number" step="1" value="100">
</div>
<div class="form-group">
<label>Price</label>
<input id="f-price" type="number" step="0.01" value="">
</div>
<button type="submit" class="btn-send">Send Order</button>
</form>
<div id="order-status"></div>
</div>
<!-- Right panel: Messages log -->
<div class="panel">
<h2>Messages <span style="font-size:12px; color:#999; font-weight:normal;">FIX Session Log</span></h2>
<div id="messages-box" class="messages-box">Loading...</div>
</div>
</div>
<script>
const SECURITIES_PRICES = {{ securities | tojson }};
function updatePrice() {
const sym = document.getElementById('f-symbol').value;
const price = SECURITIES_PRICES[sym];
if (price !== undefined) {
document.getElementById('f-price').value = price.toFixed(2);
}
}
// Works whether served at / or under /fix/
const BASE = window.location.pathname === '/' ? ''
: window.location.pathname.replace(/\/$/, '');
function setStatus(cls, text) {
document.getElementById('status-badge').className = 'status ' + cls;
document.getElementById('status-text').textContent = text;
}
async function pollStatus() {
try {
const r = await fetch(BASE + '/status');
const d = await r.json();
setStatus(d.connected ? 'connected' : 'disconnected',
d.connected ? 'CONNECTED' : 'DISCONNECTED');
} catch(e) {
setStatus('disconnected', 'DISCONNECTED');
}
}
async function doConnect() {
setStatus('connecting', 'Connecting...');
try { await fetch(BASE + '/connect'); } catch(e) {}
setTimeout(pollStatus, 2000);
}
async function doDisconnect() {
try { await fetch(BASE + '/disconnect'); } catch(e) {}
setStatus('disconnected', 'DISCONNECTED');
}
async function sendOrder(evt) {
evt.preventDefault();
const statusEl = document.getElementById('order-status');
const data = {
side: document.getElementById('f-side').value,
symbol: document.getElementById('f-symbol').value,
qty: parseFloat(document.getElementById('f-qty').value),
price: parseFloat(document.getElementById('f-price').value),
};
try {
const r = await fetch(BASE + '/order', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
const d = await r.json();
if (d.status === 'ok') {
statusEl.className = 'success';
statusEl.textContent = `Order sent: ${data.side.toUpperCase()} ${data.qty} ${data.symbol} @ ${data.price.toFixed(2)}`;
} else {
statusEl.className = 'error';
statusEl.textContent = d.message || 'Error sending order';
}
} catch(e) {
statusEl.className = 'error';
statusEl.textContent = 'Error: ' + e.message;
}
setTimeout(() => { statusEl.style.display = 'none'; }, 4000);
}
let lastMsgCount = 0;
async function pollMessages() {
try {
const r = await fetch(BASE + '/messages');
const msgs = await r.json();
if (msgs.length !== lastMsgCount) {
const box = document.getElementById('messages-box');
box.textContent = msgs.join('\n');
lastMsgCount = msgs.length;
}
} catch(e) {}
}
pollStatus();
pollMessages();
setInterval(pollStatus, 2000);
setInterval(pollMessages, 2000);
updatePrice();
</script>
</body>
</html>