Papaflessas commited on
Commit
eb27b67
·
1 Parent(s): a3c7ac0

Deploy Signal Generator app

Browse files
Files changed (2) hide show
  1. src/main.py +5 -9
  2. src/templates/index.html +82 -49
src/main.py CHANGED
@@ -200,23 +200,19 @@ async def health_check():
200
  logger.info(f"Health Check: {vitals}")
201
  return vitals
202
 
203
- from fastapi import FastAPI, HTTPException, Header, BackgroundTasks, Depends, Request
 
204
  from fastapi.responses import HTMLResponse
205
  from fastapi.templating import Jinja2Templates
206
  from fastapi.staticfiles import StaticFiles
207
-
208
- # ... imports ...
209
-
210
- app = FastAPI(title="Stock Alchemist Signal Generator")
211
 
212
  # Setup templates
213
  templates = Jinja2Templates(directory="src/templates")
214
 
215
- # ... existing code ...
216
-
217
- @app.get("/api/signals", dependencies=[Depends(verify_api_secret)])
218
  async def get_signals():
219
- """Get recent signals"""
220
  try:
221
  db = LocalDatabase()
222
  signals = db.get_recent_signals(limit=50)
 
200
  logger.info(f"Health Check: {vitals}")
201
  return vitals
202
 
203
+ # --- HTML & Public API ---
204
+
205
  from fastapi.responses import HTMLResponse
206
  from fastapi.templating import Jinja2Templates
207
  from fastapi.staticfiles import StaticFiles
208
+ from fastapi import Request
 
 
 
209
 
210
  # Setup templates
211
  templates = Jinja2Templates(directory="src/templates")
212
 
213
+ @app.get("/api/signals")
 
 
214
  async def get_signals():
215
+ """Get recent signals (Public Read-Only)"""
216
  try:
217
  db = LocalDatabase()
218
  signals = db.get_recent_signals(limit=50)
src/templates/index.html CHANGED
@@ -140,28 +140,16 @@
140
  color: var(--text-secondary);
141
  }
142
 
143
- /* Modal for secrets */
144
- #secretModal {
145
- display: flex;
146
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
147
- background: rgba(0,0,0,0.8);
148
- justify-content: center; align-items: center;
149
- z-index: 100;
150
  }
151
  </style>
152
  </head>
153
  <body>
154
 
155
- <div id="secretModal">
156
- <div class="card" style="width: 400px; text-align: center;">
157
- <h2>🔐 Enter API Secret</h2>
158
- <p style="margin-bottom: 1rem; color: var(--text-secondary); font-size: 0.9rem;">Required to perform actions.</p>
159
- <input type="password" id="apiSecretInput" placeholder="API_SECRET" />
160
- <button class="btn" onclick="saveSecret()">Access Dashboard</button>
161
- </div>
162
- </div>
163
-
164
- <div class="container" id="mainContent" style="display:none; filter: blur(5px);">
165
  <header>
166
  <h1>🧪 Stock Alchemist</h1>
167
  <div class="status-badge" id="systemStatus">● System Online</div>
@@ -227,32 +215,30 @@
227
  </div>
228
 
229
  <script>
 
230
  let API_SECRET = localStorage.getItem('api_secret');
231
 
232
- function saveSecret() {
233
- const input = document.getElementById('apiSecretInput').value;
234
- if (input) {
235
- localStorage.setItem('api_secret', input);
236
- API_SECRET = input;
237
- document.getElementById('secretModal').style.display = 'none';
238
- document.getElementById('mainContent').style.display = 'block';
239
- document.getElementById('mainContent').style.filter = 'none';
240
- init();
241
- }
242
- }
243
-
244
- if (API_SECRET) {
245
- document.getElementById('secretModal').style.display = 'none';
246
- document.getElementById('mainContent').style.display = 'block';
247
- document.getElementById('mainContent').style.filter = 'none';
248
- init();
249
- }
250
 
251
  async function init() {
252
  checkHealth();
253
  loadSignals();
254
  }
255
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  async function checkHealth() {
257
  try {
258
  const res = await fetch('/health');
@@ -274,10 +260,10 @@
274
  async function loadSignals() {
275
  const tbody = document.getElementById('signalsBody');
276
  try {
277
- const res = await fetch('/api/signals', {
278
- headers: { 'X-API-Secret': API_SECRET }
279
- });
280
- if (!res.ok) throw new Error('Auth failed');
281
 
282
  const signals = await res.json();
283
 
@@ -290,26 +276,33 @@
290
  const pos = s.signal_position || 'HOLD';
291
  const cls = pos === 'BUY' ? 'tag-buy' : (pos === 'SELL' ? 'tag-sell' : 'tag-hold');
292
  const sentiment = s.sentiment ? (s.sentiment.confidence ? `${(s.sentiment.confidence * 100).toFixed(0)}%` : '-') : '-';
293
- // Try format date
294
- const date = new Date(s.created_at).toLocaleString();
295
 
 
 
 
 
 
 
296
  return `
297
  <tr>
298
  <td style="font-weight:600; color:white;">${s.ticker}</td>
299
  <td>${s.signal_date}</td>
300
  <td><span class="tag ${cls}">${pos}</span></td>
301
  <td>${sentiment}</td>
302
- <td style="color:var(--text-secondary); font-size: 0.85rem;">${date}</td>
303
  </tr>
304
  `;
305
  }).join('');
306
 
307
  } catch (e) {
308
- tbody.innerHTML = '<tr><td colspan="5" class="empty-state" style="color:#EF4444">Failed to load signals. Check API Secret.</td></tr>';
309
  }
310
  }
311
 
312
  async function generateSignal() {
 
 
 
313
  const ticker = document.getElementById('tickerInput').value.toUpperCase();
314
  const btn = document.getElementById('genBtn');
315
  if (!ticker) return alert('Please enter a ticker');
@@ -322,7 +315,7 @@
322
  method: 'POST',
323
  headers: {
324
  'Content-Type': 'application/json',
325
- 'X-API-Secret': API_SECRET
326
  },
327
  body: JSON.stringify({ ticker: ticker })
328
  });
@@ -332,7 +325,13 @@
332
  setTimeout(loadSignals, 2000);
333
  } else {
334
  const err = await res.json();
335
- alert('Error: ' + err.detail);
 
 
 
 
 
 
336
  }
337
  } catch (e) {
338
  alert('Request failed');
@@ -343,20 +342,37 @@
343
  }
344
 
345
  async function runAnalysis() {
 
 
 
346
  if (!confirm('Run Saturday Analysis? This is a heavy background task.')) return;
347
 
348
  try {
349
  const res = await fetch('/saturday-analysis', {
350
  method: 'POST',
351
- headers: { 'X-API-Secret': API_SECRET }
352
  });
353
- alert('Analysis started in background.');
 
 
 
 
 
 
 
 
 
 
 
354
  } catch (e) {
355
  alert('Failed to start analysis');
356
  }
357
  }
358
 
359
  async function testDb() {
 
 
 
360
  const btn = event.target;
361
  const ogText = btn.innerText;
362
  btn.innerText = 'Testing...';
@@ -365,10 +381,17 @@
365
  try {
366
  const res = await fetch('/test-db', {
367
  method: 'POST',
368
- headers: { 'X-API-Secret': API_SECRET }
369
  });
370
  const data = await res.json();
371
 
 
 
 
 
 
 
 
372
  let msg = `Status: ${data.status.toUpperCase()}\n\n`;
373
  if (data.details) msg += data.details.join('\n');
374
  if (data.config) msg += `\n\nHost: ${data.config.host}\nSSL Set: ${data.config.ssl_ca_set}`;
@@ -387,6 +410,9 @@
387
  }
388
 
389
  async function testOllama() {
 
 
 
390
  const btn = event.target;
391
  const ogText = btn.innerText;
392
  btn.innerText = 'Testing...';
@@ -395,10 +421,17 @@
395
  try {
396
  const res = await fetch('/test-ollama', {
397
  method: 'POST',
398
- headers: { 'X-API-Secret': API_SECRET }
399
  });
400
  const data = await res.json();
401
 
 
 
 
 
 
 
 
402
  let msg = `Status: ${data.status.toUpperCase()}\n\n`;
403
  if (data.details) msg += data.details.join('\n');
404
 
 
140
  color: var(--text-secondary);
141
  }
142
 
143
+ /* Scale down for mobile */
144
+ @media (max-width: 600px) {
145
+ .actions { grid-template-columns: 1fr; }
146
+ h1 { font-size: 1.5rem; }
 
 
 
147
  }
148
  </style>
149
  </head>
150
  <body>
151
 
152
+ <div class="container" id="mainContent">
 
 
 
 
 
 
 
 
 
153
  <header>
154
  <h1>🧪 Stock Alchemist</h1>
155
  <div class="status-badge" id="systemStatus">● System Online</div>
 
215
  </div>
216
 
217
  <script>
218
+ // Try to get secret from storage, but don't block UI if missing
219
  let API_SECRET = localStorage.getItem('api_secret');
220
 
221
+ // Initialize immediately
222
+ init();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
  async function init() {
225
  checkHealth();
226
  loadSignals();
227
  }
228
 
229
+ function getSecretOrPrompt() {
230
+ if (!API_SECRET) {
231
+ const input = prompt("🔐 Enter API Secret to perform this action:");
232
+ if (input) {
233
+ API_SECRET = input;
234
+ localStorage.setItem('api_secret', input);
235
+ } else {
236
+ return null;
237
+ }
238
+ }
239
+ return API_SECRET;
240
+ }
241
+
242
  async function checkHealth() {
243
  try {
244
  const res = await fetch('/health');
 
260
  async function loadSignals() {
261
  const tbody = document.getElementById('signalsBody');
262
  try {
263
+ // No secret needed for viewing now
264
+ const res = await fetch('/api/signals');
265
+
266
+ if (!res.ok) throw new Error('Failed to fetch');
267
 
268
  const signals = await res.json();
269
 
 
276
  const pos = s.signal_position || 'HOLD';
277
  const cls = pos === 'BUY' ? 'tag-buy' : (pos === 'SELL' ? 'tag-sell' : 'tag-hold');
278
  const sentiment = s.sentiment ? (s.sentiment.confidence ? `${(s.sentiment.confidence * 100).toFixed(0)}%` : '-') : '-';
 
 
279
 
280
+ // Format date safely
281
+ let dateStr = s.created_at;
282
+ try {
283
+ dateStr = new Date(s.created_at).toLocaleString();
284
+ } catch(e) {}
285
+
286
  return `
287
  <tr>
288
  <td style="font-weight:600; color:white;">${s.ticker}</td>
289
  <td>${s.signal_date}</td>
290
  <td><span class="tag ${cls}">${pos}</span></td>
291
  <td>${sentiment}</td>
292
+ <td style="color:var(--text-secondary); font-size: 0.85rem;">${dateStr}</td>
293
  </tr>
294
  `;
295
  }).join('');
296
 
297
  } catch (e) {
298
+ tbody.innerHTML = '<tr><td colspan="5" class="empty-state" style="color:#EF4444">Failed to load signals.</td></tr>';
299
  }
300
  }
301
 
302
  async function generateSignal() {
303
+ const secret = getSecretOrPrompt();
304
+ if (!secret) return;
305
+
306
  const ticker = document.getElementById('tickerInput').value.toUpperCase();
307
  const btn = document.getElementById('genBtn');
308
  if (!ticker) return alert('Please enter a ticker');
 
315
  method: 'POST',
316
  headers: {
317
  'Content-Type': 'application/json',
318
+ 'X-API-Secret': secret
319
  },
320
  body: JSON.stringify({ ticker: ticker })
321
  });
 
325
  setTimeout(loadSignals, 2000);
326
  } else {
327
  const err = await res.json();
328
+ if (res.status === 403) {
329
+ alert("Invalid API Secret. Please try again.");
330
+ localStorage.removeItem('api_secret');
331
+ API_SECRET = null;
332
+ } else {
333
+ alert('Error: ' + (err.detail || 'Request failed'));
334
+ }
335
  }
336
  } catch (e) {
337
  alert('Request failed');
 
342
  }
343
 
344
  async function runAnalysis() {
345
+ const secret = getSecretOrPrompt();
346
+ if (!secret) return;
347
+
348
  if (!confirm('Run Saturday Analysis? This is a heavy background task.')) return;
349
 
350
  try {
351
  const res = await fetch('/saturday-analysis', {
352
  method: 'POST',
353
+ headers: { 'X-API-Secret': secret }
354
  });
355
+
356
+ if (res.ok) {
357
+ alert('Analysis started in background.');
358
+ } else {
359
+ if (res.status === 403) {
360
+ alert("Invalid API Secret.");
361
+ localStorage.removeItem('api_secret');
362
+ API_SECRET = null;
363
+ } else {
364
+ alert('Failed to start analysis');
365
+ }
366
+ }
367
  } catch (e) {
368
  alert('Failed to start analysis');
369
  }
370
  }
371
 
372
  async function testDb() {
373
+ const secret = getSecretOrPrompt();
374
+ if (!secret) return;
375
+
376
  const btn = event.target;
377
  const ogText = btn.innerText;
378
  btn.innerText = 'Testing...';
 
381
  try {
382
  const res = await fetch('/test-db', {
383
  method: 'POST',
384
+ headers: { 'X-API-Secret': secret }
385
  });
386
  const data = await res.json();
387
 
388
+ if (res.status === 403) {
389
+ alert("Invalid API Secret.");
390
+ localStorage.removeItem('api_secret');
391
+ API_SECRET = null;
392
+ return;
393
+ }
394
+
395
  let msg = `Status: ${data.status.toUpperCase()}\n\n`;
396
  if (data.details) msg += data.details.join('\n');
397
  if (data.config) msg += `\n\nHost: ${data.config.host}\nSSL Set: ${data.config.ssl_ca_set}`;
 
410
  }
411
 
412
  async function testOllama() {
413
+ const secret = getSecretOrPrompt();
414
+ if (!secret) return;
415
+
416
  const btn = event.target;
417
  const ogText = btn.innerText;
418
  btn.innerText = 'Testing...';
 
421
  try {
422
  const res = await fetch('/test-ollama', {
423
  method: 'POST',
424
+ headers: { 'X-API-Secret': secret }
425
  });
426
  const data = await res.json();
427
 
428
+ if (res.status === 403) {
429
+ alert("Invalid API Secret.");
430
+ localStorage.removeItem('api_secret');
431
+ API_SECRET = null;
432
+ return;
433
+ }
434
+
435
  let msg = `Status: ${data.status.toUpperCase()}\n\n`;
436
  if (data.details) msg += data.details.join('\n');
437