initial commit — cassandra v0.1
Containerised macro-strategy dashboard: 4-panel web UI (indicators, portfolio, flash news, AI strategic log), MariaDB store, hourly ingestion jobs, OpenRouter-backed AI analysis. Ports the four prototype scripts in the parent dir (market_pulse, flash_news, trading212, strategic_log) into async services backed by a persistent DB and served via FastAPI + Jinja2 + HTMX. APScheduler runs as a separate compose service for crash-safety and easier restarts. Portfolio composition + position names come live from Trading 212; news per-ticker headlines reuse those names. Tone (NOVICE/INTERMEDIATE/ PRO) and analysis style (DRY/SPECULATIVE) are env-configurable and stored on each log row so historical entries show what produced them. Default model is deepseek/deepseek-v4-flash (overridable via env). Light/dark theme toggle, sans-serif for prose surfaces, monospace for data. Bearer-token auth, OpenRouter monthly cost cap, RSS feeds auto- disabled on consecutive failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
a10409c02b
61 changed files with 4890 additions and 0 deletions
71
app/templates/base.html
Normal file
71
app/templates/base.html
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{% block title %}Cassandra{% endblock %}</title>
|
||||
{# Apply saved theme before stylesheet renders to avoid a flash. #}
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
var t = localStorage.getItem('cassandra.theme') || 'dark';
|
||||
document.documentElement.dataset.theme = t;
|
||||
} catch (e) { document.documentElement.dataset.theme = 'dark'; }
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='/css/cassandra.css') }}" />
|
||||
<script src="{{ url_for('static', path='/js/htmx.min.js') }}" defer></script>
|
||||
<script>
|
||||
// Render any <time datetime="..."> in the browser's local timezone.
|
||||
// Re-runs after every HTMX swap so freshly-loaded news rows pick up too.
|
||||
function formatLocalTimes() {
|
||||
document.querySelectorAll('time[datetime]:not([data-local])').forEach(function (t) {
|
||||
try {
|
||||
var d = new Date(t.getAttribute('datetime'));
|
||||
if (isNaN(d.getTime())) return;
|
||||
var date = d.toLocaleDateString(undefined, { day: '2-digit', month: 'short' });
|
||||
var time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
t.textContent = date + ' ' + time;
|
||||
t.title = d.toLocaleString();
|
||||
t.setAttribute('data-local', '1');
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
formatLocalTimes();
|
||||
document.body.addEventListener('htmx:afterSwap', formatLocalTimes);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<header class="app-header">
|
||||
<div class="brand">Cassandra</div>
|
||||
<nav>
|
||||
<a href="/" class="{% if request.url.path == '/' %}active{% endif %}">Dashboard</a>
|
||||
<a href="/news" class="{% if request.url.path == '/news' %}active{% endif %}">News</a>
|
||||
<a href="/log" class="{% if request.url.path.startswith('/log') %}active{% endif %}">Log</a>
|
||||
</nav>
|
||||
<div class="header-right">
|
||||
<button class="theme-toggle" type="button" aria-label="Toggle theme"
|
||||
onclick="(function(){var d=document.documentElement;var t=d.dataset.theme==='light'?'dark':'light';d.dataset.theme=t;try{localStorage.setItem('cassandra.theme',t);}catch(e){}})()">
|
||||
<span class="theme-toggle__label"></span>
|
||||
</button>
|
||||
<span class="meta">v0.1 · UTC</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="app-footer"
|
||||
hx-get="/api/health"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
id="ops-footer">
|
||||
<span class="led idle"></span> awaiting status…
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue