forked from Rockachopa/Timmy-time-dashboard
polish: extract inline CSS, add connection status, panel macro, favicon, ollama cache, toast system (#164)
Major:
- Extract all inline <style> blocks from 22 Jinja2 templates into
static/css/mission-control.css — single cacheable stylesheet
- Add tox lint check that fails on inline <style> in templates
Minor:
1. Connection status indicator in topbar (green/amber/red dot) reflecting
WebSocket + Ollama reachability, with auto-reconnect
2. Jinja2 {% macro panel(title) %} in macros.html — eliminates repeated
.card.mc-panel markup; index.html converted as example
3. SVG favicon (purple T + orange dot)
4. 30-second TTL cache on _check_ollama() to avoid blocking the event loop
on every health poll (asyncio.to_thread was already in place)
5. Toast notification system (McToast.show) for transient status messages —
wired into connection status for Ollama/WebSocket state changes
Enforcement:
- CLAUDE.md updated with conventions 11-14 (no inline CSS, use panel macro,
use toasts, never block the event loop)
- tox lint + pre-push environments now fail on inline <style> blocks
https://claude.ai/code/session_014FQ785MQdyJQ4BAXrRSo9w
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
07f2c1b41e
commit
622a6a9204
@@ -12,7 +12,9 @@
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous" />
|
||||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="/static/style.css?v=5" />
|
||||
<link rel="stylesheet" href="/static/css/mission-control.css?v=1" />
|
||||
{% block extra_styles %}{% endblock %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.3/dist/htmx.min.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
@@ -31,6 +33,10 @@
|
||||
<div class="mc-header-left">
|
||||
<a href="/" class="mc-title">MISSION CONTROL</a>
|
||||
<span class="mc-subtitle">MISSION CONTROL</span>
|
||||
<span class="mc-conn-status" id="conn-status">
|
||||
<span class="mc-conn-dot red" id="conn-dot"></span>
|
||||
<span id="conn-label">OFFLINE</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Desktop nav -->
|
||||
@@ -121,6 +127,9 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Toast container -->
|
||||
<div class="mc-toast-container" id="toast-container"></div>
|
||||
|
||||
<main class="mc-main">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
@@ -206,6 +215,100 @@
|
||||
.catch(function() {});
|
||||
}
|
||||
</script>
|
||||
<!-- Toast + connection status system -->
|
||||
<script>
|
||||
// ── Toast notifications ──
|
||||
window.McToast = {
|
||||
show: function(message, level) {
|
||||
level = level || 'info';
|
||||
var container = document.getElementById('toast-container');
|
||||
if (!container) return;
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'mc-toast ' + level;
|
||||
toast.textContent = message;
|
||||
container.appendChild(toast);
|
||||
requestAnimationFrame(function() {
|
||||
toast.classList.add('show');
|
||||
});
|
||||
setTimeout(function() {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(function() { toast.remove(); }, 300);
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Global connection status ──
|
||||
(function() {
|
||||
var dot = document.getElementById('conn-dot');
|
||||
var label = document.getElementById('conn-label');
|
||||
var wsConnected = false;
|
||||
var ollamaOk = null; // null = unknown, true/false
|
||||
|
||||
function updateIndicator() {
|
||||
if (!dot || !label) return;
|
||||
if (!wsConnected) {
|
||||
dot.className = 'mc-conn-dot red';
|
||||
label.textContent = 'OFFLINE';
|
||||
} else if (ollamaOk === false) {
|
||||
dot.className = 'mc-conn-dot amber';
|
||||
label.textContent = 'NO LLM';
|
||||
} else {
|
||||
dot.className = 'mc-conn-dot green';
|
||||
label.textContent = 'LIVE';
|
||||
}
|
||||
}
|
||||
|
||||
function checkOllama() {
|
||||
fetch('/health')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var prev = ollamaOk;
|
||||
ollamaOk = data.services && data.services.ollama === 'up';
|
||||
updateIndicator();
|
||||
if (prev === false && ollamaOk) {
|
||||
McToast.show('Ollama reconnected', 'info');
|
||||
} else if (prev === true && !ollamaOk) {
|
||||
McToast.show('Ollama unreachable', 'warn');
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
ollamaOk = false;
|
||||
updateIndicator();
|
||||
});
|
||||
}
|
||||
|
||||
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
var reconnectDelay = 1000;
|
||||
|
||||
function connectStatusWs() {
|
||||
var ws;
|
||||
try {
|
||||
ws = new WebSocket(protocol + '//' + window.location.host + '/swarm/live');
|
||||
} catch(e) { return; }
|
||||
|
||||
ws.onopen = function() {
|
||||
wsConnected = true;
|
||||
reconnectDelay = 1000;
|
||||
updateIndicator();
|
||||
checkOllama();
|
||||
};
|
||||
ws.onclose = function() {
|
||||
if (wsConnected) {
|
||||
McToast.show('WebSocket disconnected', 'error');
|
||||
}
|
||||
wsConnected = false;
|
||||
updateIndicator();
|
||||
setTimeout(connectStatusWs, reconnectDelay);
|
||||
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
||||
};
|
||||
ws.onerror = function() {};
|
||||
}
|
||||
|
||||
connectStatusWs();
|
||||
// Poll Ollama health every 30s
|
||||
setInterval(checkOllama, 30000);
|
||||
})();
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<script src="/static/notifications.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user