Almaatla commited on
Commit
1d771a0
verified
1 Parent(s): 811d19d

Create admin.html

Browse files
Files changed (1) hide show
  1. admin.html +260 -0
admin.html ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>MPTrading Admin</title>
7
+ <style>
8
+ :root{
9
+ --bg:#0b1020; --panel:#111a33; --panel2:#0f1730; --text:#e7eaf3;
10
+ --muted:#aab2d5; --line:#233055; --green:#22c55e; --red:#ef4444; --blue:#60a5fa;
11
+ }
12
+ *{ box-sizing:border-box; }
13
+ body{ margin:0; font-family:system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background:var(--bg); color:var(--text); }
14
+ header{ height:52px; display:flex; align-items:center; justify-content:space-between; padding:0 14px; border-bottom:1px solid var(--line); background:rgba(0,0,0,0.15); }
15
+ header .title{ font-weight:700; letter-spacing:0.3px; }
16
+ main{ padding:12px; display:grid; grid-template-columns: 1.2fr 1fr; gap:12px; height: calc(100vh - 52px); }
17
+ .card{ background:var(--panel); border:1px solid var(--line); border-radius:10px; overflow:hidden; display:flex; flex-direction:column; min-height:0; }
18
+ .card h2{ margin:0; padding:10px 12px; font-size:13px; color:var(--muted); text-transform:uppercase; letter-spacing:0.08em; border-bottom:1px solid var(--line); background:var(--panel2); }
19
+ .content{ padding:12px; overflow:auto; min-height:0; }
20
+
21
+ label{ display:block; font-size:12px; color:var(--muted); margin:10px 0 6px; }
22
+ input, textarea, button{
23
+ font:inherit; border-radius:10px; border:1px solid var(--line);
24
+ background:#0c1430; color:var(--text); padding:10px 12px;
25
+ }
26
+ textarea{ width:100%; min-height:260px; resize:vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size:12px; }
27
+ input{ width:100%; }
28
+ .row{ display:grid; grid-template-columns: 1fr 1fr; gap:10px; }
29
+ .row3{ display:grid; grid-template-columns: 1fr 1fr 1fr; gap:10px; }
30
+ .actions{ display:flex; gap:10px; flex-wrap:wrap; margin-top:10px; }
31
+ button{ cursor:pointer; }
32
+ button.primary:hover{ background:rgba(96,165,250,0.12); }
33
+ button.danger{ border-color:rgba(239,68,68,0.5); }
34
+ button.danger:hover{ background:rgba(239,68,68,0.12); }
35
+
36
+ table{ width:100%; border-collapse:collapse; font-size:13px; }
37
+ th, td{ text-align:left; padding:8px 6px; border-bottom:1px solid rgba(35,48,85,0.6); }
38
+ th{ color:var(--muted); font-weight:600; }
39
+ td.mono{ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
40
+ .small{ font-size:12px; color:var(--muted); }
41
+ .ok{ color:var(--green); }
42
+ .err{ color:var(--red); }
43
+ code{ color:#c7d2fe; }
44
+ </style>
45
+ </head>
46
+
47
+ <body>
48
+ <header>
49
+ <div class="title">MPTrading Admin</div>
50
+ <div class="small">Endpoints: <code>/admin/load_scenario</code>, <code>/admin/add_event</code>, <code>/admin/state</code></div>
51
+ </header>
52
+
53
+ <main>
54
+ <section class="card">
55
+ <h2>Scenario loader</h2>
56
+ <div class="content">
57
+ <label>Admin token (sent as <code>X-ADMIN-TOKEN</code>)</label>
58
+ <input id="token" type="password" placeholder="Enter ADMIN_TOKEN" />
59
+
60
+ <label>Scenario JSON</label>
61
+ <textarea id="scenario"></textarea>
62
+
63
+ <div class="actions">
64
+ <button class="primary" id="loadBtn">Load scenario</button>
65
+ <button class="danger" id="clearBtn">Clear events</button>
66
+ <button id="refreshBtn">Refresh state</button>
67
+ </div>
68
+
69
+ <div id="msg" class="small" style="margin-top:10px;"></div>
70
+
71
+ <div class="small" style="margin-top:10px;">
72
+ Scenario format:
73
+ <pre class="small" style="white-space:pre-wrap;margin:8px 0 0;color:var(--muted);">
74
+ {
75
+ "name": "FOMC week",
76
+ "startDay": 0,
77
+ "basePrice": 100.0,
78
+ "defaultVolatility": 0.8,
79
+ "events": [
80
+ { "day": 20, "shockPct": 5.0, "volatility": 1.4, "news": "Rumor of rate cut" }
81
+ ]
82
+ }
83
+ </pre>
84
+ </div>
85
+ </div>
86
+ </section>
87
+
88
+ <section class="card">
89
+ <h2>Add event & schedule</h2>
90
+ <div class="content">
91
+ <div class="row3">
92
+ <div>
93
+ <label>Offset (ticks ahead)</label>
94
+ <input id="offset" type="number" value="10" min="0" step="1" />
95
+ </div>
96
+ <div>
97
+ <label>Shock % (e.g. 5 or -3)</label>
98
+ <input id="shockPct" type="number" value="5" step="0.1" />
99
+ </div>
100
+ <div>
101
+ <label>Volatility (optional)</label>
102
+ <input id="vol" type="number" placeholder="leave blank" step="0.1" />
103
+ </div>
104
+ </div>
105
+
106
+ <label>News (optional)</label>
107
+ <input id="news" type="text" placeholder="Headline to broadcast at that tick" />
108
+
109
+ <div class="actions">
110
+ <button class="primary" id="addBtn">Add event</button>
111
+ </div>
112
+
113
+ <div style="height:12px"></div>
114
+
115
+ <div class="small">
116
+ Current day: <span class="mono" id="curDay">--</span> 路 Current volatility: <span class="mono" id="curVol">--</span>
117
+ </div>
118
+
119
+ <div style="height:10px"></div>
120
+
121
+ <table>
122
+ <thead>
123
+ <tr>
124
+ <th>Day</th>
125
+ <th>Shock%</th>
126
+ <th>Vol</th>
127
+ <th>News</th>
128
+ </tr>
129
+ </thead>
130
+ <tbody id="eventsBody">
131
+ <tr><td colspan="4" class="small">No events loaded.</td></tr>
132
+ </tbody>
133
+ </table>
134
+ </div>
135
+ </section>
136
+ </main>
137
+
138
+ <script>
139
+ const $ = (id) => document.getElementById(id);
140
+
141
+ function setMsg(text, ok=true){
142
+ $("msg").textContent = text;
143
+ $("msg").className = "small " + (ok ? "ok" : "err");
144
+ }
145
+
146
+ function headers(){
147
+ const t = $("token").value || "";
148
+ return { "Content-Type": "application/json", "X-ADMIN-TOKEN": t };
149
+ }
150
+
151
+ async function apiGet(path){
152
+ const r = await fetch(path, { method: "GET", headers: headers() });
153
+ const txt = await r.text();
154
+ let data = null;
155
+ try { data = JSON.parse(txt); } catch { data = { raw: txt }; }
156
+ if (!r.ok) throw new Error((data && data.detail) ? data.detail : ("HTTP " + r.status));
157
+ return data;
158
+ }
159
+
160
+ async function apiPost(path, body){
161
+ const r = await fetch(path, { method: "POST", headers: headers(), body: JSON.stringify(body || {}) });
162
+ const txt = await r.text();
163
+ let data = null;
164
+ try { data = JSON.parse(txt); } catch { data = { raw: txt }; }
165
+ if (!r.ok) throw new Error((data && data.detail) ? data.detail : ("HTTP " + r.status));
166
+ return data;
167
+ }
168
+
169
+ function renderEvents(events){
170
+ const tb = $("eventsBody");
171
+ tb.innerHTML = "";
172
+ if (!events || !events.length){
173
+ tb.innerHTML = `<tr><td colspan="4" class="small">No events loaded.</td></tr>`;
174
+ return;
175
+ }
176
+ for (const e of events){
177
+ const tr = document.createElement("tr");
178
+ tr.innerHTML = `
179
+ <td class="mono">${e.day}</td>
180
+ <td class="mono">${Number(e.shockPct || 0).toFixed(2)}</td>
181
+ <td class="mono">${(e.volatility === null || e.volatility === undefined) ? "" : Number(e.volatility).toFixed(2)}</td>
182
+ <td>${escapeHtml(e.news || "")}</td>
183
+ `;
184
+ tb.appendChild(tr);
185
+ }
186
+ }
187
+
188
+ function escapeHtml(s){
189
+ return String(s).replace(/[&<>"']/g, (c) => ({
190
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;"
191
+ }[c]));
192
+ }
193
+
194
+ async function refresh(){
195
+ const st = await apiGet("/admin/state");
196
+ $("curDay").textContent = st.day;
197
+ $("curVol").textContent = st.currentVolatility;
198
+ renderEvents(st.events || []);
199
+ }
200
+
201
+ // Default scenario template in textarea
202
+ $("scenario").value = JSON.stringify({
203
+ name: "Example scenario",
204
+ startDay: 0,
205
+ basePrice: 100.0,
206
+ defaultVolatility: 0.8,
207
+ events: [
208
+ { day: 20, shockPct: 5.0, volatility: 1.4, news: "Rumor of rate cut" },
209
+ { day: 30, shockPct: -3.0, volatility: 2.0, news: "Unexpected hike" }
210
+ ]
211
+ }, null, 2);
212
+
213
+ $("refreshBtn").addEventListener("click", async () => {
214
+ try { await refresh(); setMsg("State refreshed."); }
215
+ catch(e){ setMsg(e.message, false); }
216
+ });
217
+
218
+ $("clearBtn").addEventListener("click", async () => {
219
+ try { await apiPost("/admin/clear_events", {}); await refresh(); setMsg("Events cleared."); }
220
+ catch(e){ setMsg(e.message, false); }
221
+ });
222
+
223
+ $("loadBtn").addEventListener("click", async () => {
224
+ try {
225
+ const obj = JSON.parse($("scenario").value);
226
+ const res = await apiPost("/admin/load_scenario", obj);
227
+ await refresh();
228
+ setMsg(`Scenario loaded. startDay=${res.startDay}, eventsLoaded=${res.eventsLoaded}`);
229
+ } catch(e) {
230
+ setMsg(e.message, false);
231
+ }
232
+ });
233
+
234
+ $("addBtn").addEventListener("click", async () => {
235
+ try {
236
+ const offset = Number($("offset").value);
237
+ const shockPct = Number($("shockPct").value);
238
+ const volRaw = ($("vol").value || "").trim();
239
+ const volatility = volRaw === "" ? null : Number(volRaw);
240
+ const news = ($("news").value || "").trim();
241
+
242
+ const body = { offset, shockPct };
243
+ if (volatility !== null && Number.isFinite(volatility)) body.volatility = volatility;
244
+ if (news) body.news = news;
245
+
246
+ await apiPost("/admin/add_event", body);
247
+ await refresh();
248
+ setMsg("Event added.");
249
+ } catch(e) {
250
+ setMsg(e.message, false);
251
+ }
252
+ });
253
+
254
+ // Initial
255
+ refresh().catch(() => {
256
+ // likely missing token; leave quiet
257
+ });
258
+ </script>
259
+ </body>
260
+ </html>