forked from Rockachopa/Timmy-time-dashboard
polish: streamline nav, extract inline styles, improve tablet UX (#168)
This commit is contained in:
committed by
GitHub
parent
b028b768c9
commit
708c8a2477
@@ -6,7 +6,7 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="theme-color" content="#080412" />
|
<meta name="theme-color" content="#080412" />
|
||||||
<title>{% block title %}Mission Control{% endblock %}</title>
|
<title>{% block title %}Timmy Time — Mission Control{% endblock %}</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
||||||
@@ -34,35 +34,56 @@
|
|||||||
<a href="/" class="mc-title">MISSION CONTROL</a>
|
<a href="/" class="mc-title">MISSION CONTROL</a>
|
||||||
<span class="mc-subtitle">MISSION CONTROL</span>
|
<span class="mc-subtitle">MISSION CONTROL</span>
|
||||||
<span class="mc-conn-status" id="conn-status">
|
<span class="mc-conn-status" id="conn-status">
|
||||||
<span class="mc-conn-dot red" id="conn-dot"></span>
|
<span class="mc-conn-dot amber" id="conn-dot"></span>
|
||||||
<span id="conn-label">OFFLINE</span>
|
<span id="conn-label">CONNECTING</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Desktop nav -->
|
<!-- Desktop nav — grouped dropdowns matching mobile sections -->
|
||||||
<div class="mc-header-right mc-desktop-nav">
|
<div class="mc-header-right mc-desktop-nav">
|
||||||
<a href="/calm" class="mc-test-link">CALM</a>
|
<a href="/" class="mc-test-link">HOME</a>
|
||||||
<a href="/tasks" class="mc-test-link">TASKS</a>
|
<div class="mc-nav-dropdown">
|
||||||
<a href="/briefing" class="mc-test-link">BRIEFING</a>
|
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">CORE ▾</button>
|
||||||
<a href="/thinking" class="mc-test-link mc-link-thinking">THINKING</a>
|
<div class="mc-dropdown-menu">
|
||||||
<a href="/swarm/mission-control" class="mc-test-link">MISSION CTRL</a>
|
<a href="/calm" class="mc-test-link">CALM</a>
|
||||||
<a href="/swarm/live" class="mc-test-link">SWARM</a>
|
<a href="/tasks" class="mc-test-link">TASKS</a>
|
||||||
<a href="/bugs" class="mc-test-link mc-link-bugs">BUGS</a>
|
<a href="/briefing" class="mc-test-link">BRIEFING</a>
|
||||||
|
<a href="/thinking" class="mc-test-link mc-link-thinking">THINKING</a>
|
||||||
|
<a href="/swarm/mission-control" class="mc-test-link">MISSION CTRL</a>
|
||||||
|
<a href="/swarm/live" class="mc-test-link">SWARM</a>
|
||||||
|
<a href="/bugs" class="mc-test-link mc-link-bugs">BUGS</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mc-nav-dropdown">
|
||||||
|
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">AGENTS ▾</button>
|
||||||
|
<div class="mc-dropdown-menu">
|
||||||
|
<a href="/hands" class="mc-test-link">HANDS</a>
|
||||||
|
<a href="/work-orders/queue" class="mc-test-link">WORK ORDERS</a>
|
||||||
|
<a href="/self-modify/queue" class="mc-test-link">UPGRADES</a>
|
||||||
|
<a href="/self-coding" class="mc-test-link">SELF-CODING</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mc-nav-dropdown">
|
||||||
|
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">INTEL ▾</button>
|
||||||
|
<div class="mc-dropdown-menu">
|
||||||
|
<a href="/spark/ui" class="mc-test-link">SPARK</a>
|
||||||
|
<a href="/memory" class="mc-test-link">MEMORY</a>
|
||||||
|
<a href="/marketplace/ui" class="mc-test-link">MARKET</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mc-nav-dropdown">
|
||||||
|
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">SYSTEM ▾</button>
|
||||||
|
<div class="mc-dropdown-menu">
|
||||||
|
<a href="/tools" class="mc-test-link">TOOLS</a>
|
||||||
|
<a href="/swarm/events" class="mc-test-link">EVENTS</a>
|
||||||
|
<a href="/router/status" class="mc-test-link">ROUTER</a>
|
||||||
|
<a href="/grok/status" class="mc-test-link mc-link-grok">GROK</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="mc-nav-dropdown">
|
<div class="mc-nav-dropdown">
|
||||||
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">MORE ▾</button>
|
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">MORE ▾</button>
|
||||||
<div class="mc-dropdown-menu">
|
<div class="mc-dropdown-menu">
|
||||||
<a href="/spark/ui" class="mc-test-link">SPARK</a>
|
|
||||||
<a href="/marketplace/ui" class="mc-test-link">MARKET</a>
|
|
||||||
<a href="/tools" class="mc-test-link">TOOLS</a>
|
|
||||||
<a href="/swarm/events" class="mc-test-link">EVENTS</a>
|
|
||||||
<a href="/lightning/ledger" class="mc-test-link">LEDGER</a>
|
<a href="/lightning/ledger" class="mc-test-link">LEDGER</a>
|
||||||
<a href="/memory" class="mc-test-link">MEMORY</a>
|
|
||||||
<a href="/router/status" class="mc-test-link">ROUTER</a>
|
|
||||||
<a href="/grok/status" class="mc-test-link mc-link-grok">GROK</a>
|
|
||||||
<a href="/self-modify/queue" class="mc-test-link">UPGRADES</a>
|
|
||||||
<a href="/self-coding" class="mc-test-link">SELF-CODING</a>
|
|
||||||
<a href="/hands" class="mc-test-link">HANDS</a>
|
|
||||||
<a href="/work-orders/queue" class="mc-test-link">WORK ORDERS</a>
|
|
||||||
<a href="/creative/ui" class="mc-test-link">CREATIVE</a>
|
<a href="/creative/ui" class="mc-test-link">CREATIVE</a>
|
||||||
<a href="/voice/button" class="mc-test-link">VOICE</a>
|
<a href="/voice/button" class="mc-test-link">VOICE</a>
|
||||||
<a href="/mobile" class="mc-test-link" title="Mobile-optimized view">MOBILE</a>
|
<a href="/mobile" class="mc-test-link" title="Mobile-optimized view">MOBILE</a>
|
||||||
@@ -70,10 +91,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mc-nav-dropdown" id="notif-dropdown">
|
<div class="mc-nav-dropdown" id="notif-dropdown">
|
||||||
<button id="enable-notifications" class="mc-test-link mc-dropdown-toggle" style="background:none;cursor:pointer;position:relative;" title="Notifications" aria-expanded="false">🔔<span id="notif-badge" class="notif-badge" style="display:none;"></span></button>
|
<button id="enable-notifications" class="mc-test-link mc-dropdown-toggle mc-notif-btn" title="Notifications" aria-expanded="false">🔔<span id="notif-badge" class="notif-badge"></span></button>
|
||||||
<div class="mc-dropdown-menu" style="right:0;left:auto;min-width:280px;max-height:350px;overflow-y:auto;">
|
<div class="mc-dropdown-menu mc-notif-menu">
|
||||||
<div id="notif-list" style="padding:6px;">
|
<div id="notif-list" class="mc-notif-list">
|
||||||
<div style="color:var(--text-dim);font-size:0.8rem;text-align:center;padding:12px;">Loading...</div>
|
<div class="mc-notif-empty">Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +144,7 @@
|
|||||||
<a href="/mobile" class="mc-mobile-link">MOBILE</a>
|
<a href="/mobile" class="mc-mobile-link">MOBILE</a>
|
||||||
<a href="/mobile/local" class="mc-mobile-link">LOCAL AI</a>
|
<a href="/mobile/local" class="mc-mobile-link">LOCAL AI</a>
|
||||||
<div class="mc-mobile-menu-footer">
|
<div class="mc-mobile-menu-footer">
|
||||||
<button id="enable-notifications-mobile" class="mc-mobile-link" style="background:none;border:none;cursor:pointer;width:100%;text-align:left;font:inherit;color:inherit;padding:inherit;">🔔 NOTIFICATIONS</button>
|
<button id="enable-notifications-mobile" class="mc-mobile-link mc-mobile-notif-btn">🔔 NOTIFICATIONS</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -159,9 +180,10 @@
|
|||||||
hamburger.addEventListener('click', toggleMenu);
|
hamburger.addEventListener('click', toggleMenu);
|
||||||
overlay.addEventListener('click', toggleMenu);
|
overlay.addEventListener('click', toggleMenu);
|
||||||
|
|
||||||
// Highlight current page in mobile menu
|
// Highlight current page in mobile menu and desktop dropdowns
|
||||||
document.querySelectorAll('.mc-mobile-link').forEach(function(a) {
|
var currentPath = window.location.pathname;
|
||||||
if (a.getAttribute('href') === window.location.pathname) {
|
document.querySelectorAll('.mc-mobile-link, .mc-dropdown-menu .mc-test-link').forEach(function(a) {
|
||||||
|
if (a.getAttribute('href') === currentPath) {
|
||||||
a.classList.add('active');
|
a.classList.add('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -200,17 +222,25 @@
|
|||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
var list = document.getElementById('notif-list');
|
var list = document.getElementById('notif-list');
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
list.innerHTML = '<div style="color:var(--text-dim);font-size:0.8rem;text-align:center;padding:12px;">No recent notifications</div>';
|
list.innerHTML = '<div class="mc-notif-empty">No recent notifications</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = data.map(function(n) {
|
list.innerHTML = '';
|
||||||
return '<div style="padding:6px 8px;border-bottom:1px solid var(--border);font-size:0.8rem;">'
|
data.forEach(function(n) {
|
||||||
+ '<div style="color:var(--text-bright);">' + (n.title || n.event_type || 'Event') + '</div>'
|
var item = document.createElement('div');
|
||||||
+ '<div style="color:var(--text-dim);font-size:0.7rem;">' + (n.timestamp || '') + '</div>'
|
item.className = 'mc-notif-item';
|
||||||
+ '</div>';
|
var title = document.createElement('div');
|
||||||
}).join('');
|
title.className = 'mc-notif-title';
|
||||||
|
title.textContent = n.title || n.event_type || 'Event';
|
||||||
|
var ts = document.createElement('div');
|
||||||
|
ts.className = 'mc-notif-ts';
|
||||||
|
ts.textContent = n.timestamp || '';
|
||||||
|
item.appendChild(title);
|
||||||
|
item.appendChild(ts);
|
||||||
|
list.appendChild(item);
|
||||||
|
});
|
||||||
var badge = document.getElementById('notif-badge');
|
var badge = document.getElementById('notif-badge');
|
||||||
if (badge) { badge.style.display = 'none'; }
|
if (badge) { badge.classList.add('hidden'); }
|
||||||
})
|
})
|
||||||
.catch(function() {});
|
.catch(function() {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<!-- Agents (HTMX-polled from registry) -->
|
<!-- Agents (HTMX-polled from registry) -->
|
||||||
{% call panel("AGENTS", hx_get="/swarm/agents/sidebar", hx_trigger="every 10s") %}
|
{% call panel("AGENTS", hx_get="/swarm/agents/sidebar", hx_trigger="every 10s") %}
|
||||||
<div style="font-size:11px; color:var(--text-dim); letter-spacing:.08em;">LOADING...</div>
|
<div class="mc-loading-placeholder">LOADING...</div>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
<!-- System Health (HTMX polled) -->
|
<!-- System Health (HTMX polled) -->
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Mission Control — Timmy Time{% endblock %}
|
{% block title %}System Overview — Timmy Time{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">🎛️ Mission Control</h2>
|
<h2 class="card-title">System Overview</h2>
|
||||||
<div>
|
<div>
|
||||||
<span class="badge badge-success" id="system-status">Loading...</span>
|
<span class="badge badge-success" id="system-status">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sovereignty Score -->
|
<!-- Sovereignty Score -->
|
||||||
<div style="margin-bottom: 24px;">
|
<div class="sov-section">
|
||||||
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 12px;">
|
<div class="sov-header">
|
||||||
<div style="font-size: 3rem; font-weight: 700;" id="sov-score">-</div>
|
<div class="sov-score-value" id="sov-score">-</div>
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: 600;">Sovereignty Score</div>
|
<div class="sov-score-label">Sovereignty Score</div>
|
||||||
<div style="font-size: 0.875rem; color: var(--text-muted);" id="sov-label">Calculating...</div>
|
<div class="sov-score-detail" id="sov-label">Calculating...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="background: var(--bg-tertiary); height: 8px; border-radius: 4px; overflow: hidden;">
|
<div class="sov-bar-track">
|
||||||
<div id="sov-bar" style="background: var(--success); height: 100%; width: 0%; transition: width 0.5s;"></div>
|
<div id="sov-bar" class="sov-bar-fill"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dependency Grid -->
|
<!-- Dependency Grid -->
|
||||||
<h3 style="margin-bottom: 12px;">Dependencies</h3>
|
<h3 class="mc-section-heading">Dependencies</h3>
|
||||||
<div class="grid grid-2" id="dependency-grid" style="margin-bottom: 24px;">
|
<div class="grid grid-2 mc-section-gap" id="dependency-grid">
|
||||||
<p style="color: var(--text-muted);">Loading...</p>
|
<p class="chat-history-placeholder">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recommendations -->
|
<!-- Recommendations -->
|
||||||
<h3 style="margin-bottom: 12px;">Recommendations</h3>
|
<h3 class="mc-section-heading">Recommendations</h3>
|
||||||
<div id="recommendations" style="margin-bottom: 24px;">
|
<div id="recommendations" class="mc-section-gap">
|
||||||
<p style="color: var(--text-muted);">Loading...</p>
|
<p class="chat-history-placeholder">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- System Metrics -->
|
<!-- System Metrics -->
|
||||||
<h3 style="margin-bottom: 12px;">System Metrics</h3>
|
<h3 class="mc-section-heading">System Metrics</h3>
|
||||||
<div class="grid grid-4" id="metrics-grid">
|
<div class="grid grid-4" id="metrics-grid">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value" id="metric-uptime">-</div>
|
<div class="stat-value" id="metric-uptime">-</div>
|
||||||
@@ -60,11 +60,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Grok Mode Toggle -->
|
<!-- Grok Mode Toggle -->
|
||||||
<div class="card" style="margin-top: 24px;">
|
<div class="card mc-card-spaced">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">Grok Mode</h2>
|
<h2 class="card-title">Grok Mode</h2>
|
||||||
<div>
|
<div>
|
||||||
<span class="badge" id="grok-badge" style="background: #666;">STANDBY</span>
|
<span class="badge grok-badge-standby" id="grok-badge">STANDBY</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="grok-toggle-card"
|
<div id="grok-toggle-card"
|
||||||
@@ -72,29 +72,26 @@
|
|||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-target="#grok-toggle-card"
|
hx-target="#grok-toggle-card"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<div style="border: 2px solid #666; border-radius: 12px; padding: 16px;
|
<div class="grok-toggle-box">
|
||||||
background: var(--bg-secondary);">
|
<div class="grok-toggle-row">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: 700; font-size: 1.1rem; color: #666;">
|
<div class="grok-toggle-title">
|
||||||
GROK MODE: LOADING...
|
GROK MODE: LOADING...
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.8rem; color: var(--text-muted); margin-top: 4px;">
|
<div class="grok-toggle-desc">
|
||||||
xAI frontier reasoning augmentation
|
xAI frontier reasoning augmentation
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button hx-post="/grok/toggle"
|
<button hx-post="/grok/toggle"
|
||||||
hx-target="#grok-toggle-card"
|
hx-target="#grok-toggle-card"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
style="background: #666; color: #000; border: none;
|
class="grok-activate-btn">
|
||||||
border-radius: 8px; padding: 8px 20px; cursor: pointer;
|
|
||||||
font-weight: 700; font-family: inherit;">
|
|
||||||
ACTIVATE
|
ACTIVATE
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-3" style="margin-top: 12px;">
|
<div class="grid grid-3 mc-section-heading">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value" id="grok-requests">0</div>
|
<div class="stat-value" id="grok-requests">0</div>
|
||||||
<div class="stat-label">Grok Queries</div>
|
<div class="stat-label">Grok Queries</div>
|
||||||
@@ -111,14 +108,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Heartbeat Monitor -->
|
<!-- Heartbeat Monitor -->
|
||||||
<div class="card" style="margin-top: 24px;">
|
<div class="card mc-card-spaced">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">Heartbeat Monitor</h2>
|
<h2 class="card-title">Heartbeat Monitor</h2>
|
||||||
<div>
|
<div>
|
||||||
<span class="badge" id="heartbeat-status">Checking...</span>
|
<span class="badge" id="heartbeat-status">Checking...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-3">
|
<div class="grid grid-3">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value" id="hb-tick">-</div>
|
<div class="stat-value" id="hb-tick">-</div>
|
||||||
@@ -133,25 +130,25 @@
|
|||||||
<div class="stat-label">Model</div>
|
<div class="stat-label">Model</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 16px;">
|
<div class="hb-log-wrap">
|
||||||
<div id="heartbeat-log" style="height: 100px; overflow-y: auto; background: var(--bg-tertiary); padding: 12px; border-radius: 8px; font-family: monospace; font-size: 0.75rem;">
|
<div id="heartbeat-log" class="hb-log">
|
||||||
<div style="color: var(--text-muted);">Waiting for heartbeat...</div>
|
<div class="hb-log-placeholder">Waiting for heartbeat...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat History -->
|
<!-- Chat History -->
|
||||||
<div class="card" style="margin-top: 24px;">
|
<div class="card mc-card-spaced">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">💬 Chat History</h2>
|
<h2 class="card-title">Chat History</h2>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-sm" onclick="loadChatHistory()">Refresh</button>
|
<button class="btn btn-sm" onclick="loadChatHistory()">Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="chat-history" style="max-height: 300px; overflow-y: auto;">
|
<div id="chat-history" class="chat-history-container">
|
||||||
<p style="color: var(--text-muted);">Loading chat history...</p>
|
<p class="chat-history-placeholder">Loading chat history...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -161,22 +158,24 @@ async function loadSovereignty() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/health/sovereignty');
|
const response = await fetch('/health/sovereignty');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Update score
|
// Update score
|
||||||
document.getElementById('sov-score').textContent = data.overall_score.toFixed(1);
|
var scoreEl = document.getElementById('sov-score');
|
||||||
document.getElementById('sov-score').style.color = data.overall_score >= 9 ? 'var(--success)' :
|
scoreEl.textContent = data.overall_score.toFixed(1);
|
||||||
data.overall_score >= 7 ? 'var(--warning)' : 'var(--danger)';
|
scoreEl.style.color = data.overall_score >= 9 ? 'var(--success)' :
|
||||||
document.getElementById('sov-bar').style.width = (data.overall_score * 10) + '%';
|
data.overall_score >= 7 ? 'var(--warning)' : 'var(--danger)';
|
||||||
document.getElementById('sov-bar').style.background = data.overall_score >= 9 ? 'var(--success)' :
|
var barEl = document.getElementById('sov-bar');
|
||||||
data.overall_score >= 7 ? 'var(--warning)' : 'var(--danger)';
|
barEl.style.width = (data.overall_score * 10) + '%';
|
||||||
|
barEl.style.background = data.overall_score >= 9 ? 'var(--success)' :
|
||||||
|
data.overall_score >= 7 ? 'var(--warning)' : 'var(--danger)';
|
||||||
|
|
||||||
// Update label
|
// Update label
|
||||||
let label = 'Poor';
|
let label = 'Poor';
|
||||||
if (data.overall_score >= 9) label = 'Excellent';
|
if (data.overall_score >= 9) label = 'Excellent';
|
||||||
else if (data.overall_score >= 8) label = 'Good';
|
else if (data.overall_score >= 8) label = 'Good';
|
||||||
else if (data.overall_score >= 6) label = 'Fair';
|
else if (data.overall_score >= 6) label = 'Fair';
|
||||||
document.getElementById('sov-label').textContent = `${label} — ${data.dependencies.length} dependencies checked`;
|
document.getElementById('sov-label').textContent = label + ' \u2014 ' + data.dependencies.length + ' dependencies checked';
|
||||||
|
|
||||||
// Update system status
|
// Update system status
|
||||||
const systemStatus = document.getElementById('system-status');
|
const systemStatus = document.getElementById('system-status');
|
||||||
if (data.overall_score >= 9) {
|
if (data.overall_score >= 9) {
|
||||||
@@ -189,67 +188,67 @@ async function loadSovereignty() {
|
|||||||
systemStatus.textContent = 'Degraded';
|
systemStatus.textContent = 'Degraded';
|
||||||
systemStatus.className = 'badge badge-danger';
|
systemStatus.className = 'badge badge-danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update dependency grid
|
// Update dependency grid
|
||||||
const grid = document.getElementById('dependency-grid');
|
const grid = document.getElementById('dependency-grid');
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
data.dependencies.forEach(dep => {
|
data.dependencies.forEach(function(dep) {
|
||||||
const card = document.createElement('div');
|
var card = document.createElement('div');
|
||||||
card.className = 'card';
|
card.className = 'card';
|
||||||
card.style.padding = '12px';
|
card.style.padding = '12px';
|
||||||
|
|
||||||
const statusColor = dep.status === 'healthy' ? 'var(--success)' :
|
var statusColor = dep.status === 'healthy' ? 'var(--success)' :
|
||||||
dep.status === 'degraded' ? 'var(--warning)' : 'var(--danger)';
|
dep.status === 'degraded' ? 'var(--warning)' : 'var(--danger)';
|
||||||
const scoreColor = dep.sovereignty_score >= 9 ? 'var(--success)' :
|
var scoreColor = dep.sovereignty_score >= 9 ? 'var(--success)' :
|
||||||
dep.sovereignty_score >= 7 ? 'var(--warning)' : 'var(--danger)';
|
dep.sovereignty_score >= 7 ? 'var(--warning)' : 'var(--danger)';
|
||||||
|
|
||||||
// Securely build card content using textContent for dynamic data
|
var header = document.createElement('div');
|
||||||
const header = document.createElement('div');
|
header.className = 'dep-card-header';
|
||||||
header.style = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;';
|
|
||||||
|
var name = document.createElement('strong');
|
||||||
const name = document.createElement('strong');
|
|
||||||
name.textContent = dep.name;
|
name.textContent = dep.name;
|
||||||
|
|
||||||
const badge = document.createElement('span');
|
var badge = document.createElement('span');
|
||||||
badge.className = 'badge';
|
badge.className = 'badge';
|
||||||
badge.style.background = statusColor;
|
badge.style.background = statusColor;
|
||||||
badge.textContent = dep.status;
|
badge.textContent = dep.status;
|
||||||
|
|
||||||
header.appendChild(name);
|
header.appendChild(name);
|
||||||
header.appendChild(badge);
|
header.appendChild(badge);
|
||||||
|
|
||||||
const details = document.createElement('div');
|
var details = document.createElement('div');
|
||||||
details.style = 'font-size: 0.875rem; color: var(--text-muted); margin-bottom: 8px;';
|
details.className = 'dep-card-details';
|
||||||
details.textContent = dep.details.error || dep.details.note || 'Operating normally';
|
details.textContent = dep.details.error || dep.details.note || 'Operating normally';
|
||||||
|
|
||||||
const score = document.createElement('div');
|
var score = document.createElement('div');
|
||||||
score.style = `font-size: 0.75rem; color: ${scoreColor};`;
|
score.className = 'dep-card-score';
|
||||||
score.textContent = `Sovereignty: ${dep.sovereignty_score}/10`;
|
score.style.color = scoreColor;
|
||||||
|
score.textContent = 'Sovereignty: ' + dep.sovereignty_score + '/10';
|
||||||
|
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
card.appendChild(details);
|
card.appendChild(details);
|
||||||
card.appendChild(score);
|
card.appendChild(score);
|
||||||
grid.appendChild(card);
|
grid.appendChild(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update recommendations securely
|
// Update recommendations securely
|
||||||
const recs = document.getElementById('recommendations');
|
const recs = document.getElementById('recommendations');
|
||||||
recs.innerHTML = '';
|
recs.innerHTML = '';
|
||||||
if (data.recommendations && data.recommendations.length > 0) {
|
if (data.recommendations && data.recommendations.length > 0) {
|
||||||
const ul = document.createElement('ul');
|
var ul = document.createElement('ul');
|
||||||
data.recommendations.forEach(r => {
|
data.recommendations.forEach(function(r) {
|
||||||
const li = document.createElement('li');
|
var li = document.createElement('li');
|
||||||
li.textContent = r;
|
li.textContent = r;
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
recs.appendChild(ul);
|
recs.appendChild(ul);
|
||||||
} else {
|
} else {
|
||||||
const p = document.createElement('p');
|
var p = document.createElement('p');
|
||||||
p.style.color = 'var(--text-muted)';
|
p.className = 'chat-history-placeholder';
|
||||||
p.textContent = 'No recommendations — system optimal';
|
p.textContent = 'No recommendations \u2014 system optimal';
|
||||||
recs.appendChild(p);
|
recs.appendChild(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load sovereignty:', error);
|
console.error('Failed to load sovereignty:', error);
|
||||||
document.getElementById('system-status').textContent = 'Error';
|
document.getElementById('system-status').textContent = 'Error';
|
||||||
@@ -264,15 +263,14 @@ async function loadHealth() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Format uptime
|
// Format uptime
|
||||||
const uptime = data.uptime_seconds;
|
var uptime = data.uptime_seconds;
|
||||||
let uptimeStr;
|
var uptimeStr;
|
||||||
if (uptime < 60) uptimeStr = Math.floor(uptime) + 's';
|
if (uptime < 60) uptimeStr = Math.floor(uptime) + 's';
|
||||||
else if (uptime < 3600) uptimeStr = Math.floor(uptime / 60) + 'm';
|
else if (uptime < 3600) uptimeStr = Math.floor(uptime / 60) + 'm';
|
||||||
else uptimeStr = Math.floor(uptime / 3600) + 'h ' + Math.floor((uptime % 3600) / 60) + 'm';
|
else uptimeStr = Math.floor(uptime / 3600) + 'h ' + Math.floor((uptime % 3600) / 60) + 'm';
|
||||||
|
|
||||||
document.getElementById('metric-uptime').textContent = uptimeStr;
|
document.getElementById('metric-uptime').textContent = uptimeStr;
|
||||||
|
|
||||||
// LLM backend and model from /health response
|
|
||||||
if (data.llm_backend) {
|
if (data.llm_backend) {
|
||||||
document.getElementById('hb-backend').textContent = data.llm_backend;
|
document.getElementById('hb-backend').textContent = data.llm_backend;
|
||||||
}
|
}
|
||||||
@@ -292,11 +290,10 @@ async function loadSwarmStats() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
var agentCount = data.agents || 0;
|
var agentCount = data.agents || 0;
|
||||||
// Fallback: if /swarm returns 0, try /swarm/agents for a direct count
|
|
||||||
if (agentCount === 0) {
|
if (agentCount === 0) {
|
||||||
try {
|
try {
|
||||||
const agentResp = await fetch('/swarm/agents');
|
var agentResp = await fetch('/swarm/agents');
|
||||||
const agentData = await agentResp.json();
|
var agentData = await agentResp.json();
|
||||||
if (Array.isArray(agentData.agents)) {
|
if (Array.isArray(agentData.agents)) {
|
||||||
agentCount = agentData.agents.length;
|
agentCount = agentData.agents.length;
|
||||||
}
|
}
|
||||||
@@ -316,32 +313,36 @@ async function loadLightningStats() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/serve/status');
|
const response = await fetch('/serve/status');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
document.getElementById('metric-earned').textContent = data.total_earned_sats || 0;
|
document.getElementById('metric-earned').textContent = data.total_earned_sats || 0;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// /serve may not be running — default to 0 instead of '-'
|
|
||||||
document.getElementById('metric-earned').textContent = '0';
|
document.getElementById('metric-earned').textContent = '0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heartbeat simulation
|
// Heartbeat simulation
|
||||||
let tickCount = 0;
|
var tickCount = 0;
|
||||||
function updateHeartbeat() {
|
function updateHeartbeat() {
|
||||||
tickCount++;
|
tickCount++;
|
||||||
const now = new Date().toLocaleTimeString();
|
var now = new Date().toLocaleTimeString();
|
||||||
document.getElementById('hb-tick').textContent = now;
|
document.getElementById('hb-tick').textContent = now;
|
||||||
document.getElementById('heartbeat-status').textContent = 'Active';
|
document.getElementById('heartbeat-status').textContent = 'Active';
|
||||||
document.getElementById('heartbeat-status').className = 'badge badge-success';
|
document.getElementById('heartbeat-status').className = 'badge badge-success';
|
||||||
|
|
||||||
const log = document.getElementById('heartbeat-log');
|
var log = document.getElementById('heartbeat-log');
|
||||||
const entry = document.createElement('div');
|
var entry = document.createElement('div');
|
||||||
entry.style.marginBottom = '2px';
|
entry.className = 'hb-entry';
|
||||||
entry.innerHTML = `<span style="color: var(--text-muted);">[${now}]</span> <span style="color: var(--success);">✓</span> Tick ${tickCount}`;
|
var ts = document.createElement('span');
|
||||||
|
ts.className = 'hb-entry-ts';
|
||||||
|
ts.textContent = '[' + now + ']';
|
||||||
|
var check = document.createElement('span');
|
||||||
|
check.className = 'hb-entry-ok';
|
||||||
|
check.textContent = ' \u2713 ';
|
||||||
|
entry.appendChild(ts);
|
||||||
|
entry.appendChild(check);
|
||||||
|
entry.appendChild(document.createTextNode('Tick ' + tickCount));
|
||||||
log.appendChild(entry);
|
log.appendChild(entry);
|
||||||
log.scrollTop = log.scrollHeight;
|
log.scrollTop = log.scrollHeight;
|
||||||
|
|
||||||
// Keep only last 50 entries
|
|
||||||
while (log.children.length > 50) {
|
while (log.children.length > 50) {
|
||||||
log.removeChild(log.firstChild);
|
log.removeChild(log.firstChild);
|
||||||
}
|
}
|
||||||
@@ -349,55 +350,63 @@ function updateHeartbeat() {
|
|||||||
|
|
||||||
// Load chat history
|
// Load chat history
|
||||||
async function loadChatHistory() {
|
async function loadChatHistory() {
|
||||||
const container = document.getElementById('chat-history');
|
var container = document.getElementById('chat-history');
|
||||||
container.innerHTML = '<p style="color: var(--text-muted);">Loading...</p>';
|
container.innerHTML = '';
|
||||||
|
var loadingP = document.createElement('p');
|
||||||
|
loadingP.className = 'chat-history-placeholder';
|
||||||
|
loadingP.textContent = 'Loading...';
|
||||||
|
container.appendChild(loadingP);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to load from the message log endpoint if available
|
var response = await fetch('/dashboard/messages');
|
||||||
const response = await fetch('/dashboard/messages');
|
var messages = await response.json();
|
||||||
const messages = await response.json();
|
|
||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
container.innerHTML = '<p style="color: var(--text-muted);">No messages yet</p>';
|
container.innerHTML = '';
|
||||||
|
var emptyP = document.createElement('p');
|
||||||
|
emptyP.className = 'chat-history-placeholder';
|
||||||
|
emptyP.textContent = 'No messages yet';
|
||||||
|
container.appendChild(emptyP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
messages.slice(-20).forEach(msg => {
|
messages.slice(-20).forEach(function(msg) {
|
||||||
const div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.style.marginBottom = '12px';
|
div.className = msg.role === 'user' ? 'chat-history-msg chat-history-msg--user' : 'chat-history-msg';
|
||||||
div.style.padding = '8px';
|
|
||||||
div.style.background = msg.role === 'user' ? 'var(--bg-tertiary)' : 'transparent';
|
var role = document.createElement('strong');
|
||||||
div.style.borderRadius = '4px';
|
|
||||||
|
|
||||||
const role = document.createElement('strong');
|
|
||||||
role.textContent = msg.role === 'user' ? 'You: ' : 'Timmy: ';
|
role.textContent = msg.role === 'user' ? 'You: ' : 'Timmy: ';
|
||||||
role.style.color = msg.role === 'user' ? 'var(--accent)' : 'var(--success)';
|
role.style.color = msg.role === 'user' ? 'var(--accent)' : 'var(--success)';
|
||||||
|
|
||||||
const content = document.createElement('span');
|
var content = document.createElement('span');
|
||||||
content.textContent = msg.content;
|
content.textContent = msg.content;
|
||||||
|
|
||||||
div.appendChild(role);
|
div.appendChild(role);
|
||||||
div.appendChild(content);
|
div.appendChild(content);
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Fallback: show placeholder
|
container.innerHTML = '';
|
||||||
container.innerHTML = `
|
var fallback = document.createElement('div');
|
||||||
<div style="color: var(--text-muted); text-align: center; padding: 20px;">
|
fallback.className = 'chat-history-fallback';
|
||||||
<p>Chat history persistence coming soon</p>
|
var p1 = document.createElement('p');
|
||||||
<p style="font-size: 0.875rem;">Messages are currently in-memory only</p>
|
p1.textContent = 'Chat history persistence coming soon';
|
||||||
</div>
|
var p2 = document.createElement('p');
|
||||||
`;
|
p2.className = 'chat-history-fallback-sub';
|
||||||
|
p2.textContent = 'Messages are currently in-memory only';
|
||||||
|
fallback.appendChild(p1);
|
||||||
|
fallback.appendChild(p2);
|
||||||
|
container.appendChild(fallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Grok stats
|
// Load Grok stats
|
||||||
async function loadGrokStats() {
|
async function loadGrokStats() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/grok/status');
|
var response = await fetch('/grok/status');
|
||||||
const data = await response.json();
|
var data = await response.json();
|
||||||
|
|
||||||
if (data.stats) {
|
if (data.stats) {
|
||||||
document.getElementById('grok-requests').textContent = data.stats.total_requests || 0;
|
document.getElementById('grok-requests').textContent = data.stats.total_requests || 0;
|
||||||
@@ -406,15 +415,13 @@ async function loadGrokStats() {
|
|||||||
document.getElementById('grok-cost').textContent = data.stats.estimated_cost_sats || 0;
|
document.getElementById('grok-cost').textContent = data.stats.estimated_cost_sats || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const badge = document.getElementById('grok-badge');
|
var badge = document.getElementById('grok-badge');
|
||||||
if (data.active) {
|
if (data.active) {
|
||||||
badge.textContent = 'ACTIVE';
|
badge.textContent = 'ACTIVE';
|
||||||
badge.style.background = '#00ff88';
|
badge.className = 'badge grok-badge-active';
|
||||||
badge.style.color = '#000';
|
|
||||||
} else {
|
} else {
|
||||||
badge.textContent = 'STANDBY';
|
badge.textContent = 'STANDBY';
|
||||||
badge.style.background = '#666';
|
badge.className = 'badge grok-badge-standby';
|
||||||
badge.style.color = '#fff';
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Grok endpoint may not respond — silent fallback
|
// Grok endpoint may not respond — silent fallback
|
||||||
@@ -430,10 +437,10 @@ loadGrokStats();
|
|||||||
loadChatHistory();
|
loadChatHistory();
|
||||||
|
|
||||||
// Periodic updates
|
// Periodic updates
|
||||||
setInterval(loadSovereignty, 30000); // Every 30s
|
setInterval(loadSovereignty, 30000);
|
||||||
setInterval(loadHealth, 10000); // Every 10s
|
setInterval(loadHealth, 10000);
|
||||||
setInterval(loadSwarmStats, 5000); // Every 5s
|
setInterval(loadSwarmStats, 5000);
|
||||||
setInterval(updateHeartbeat, 5000); // Heartbeat every 5s
|
setInterval(updateHeartbeat, 5000);
|
||||||
setInterval(loadGrokStats, 10000); // Grok stats every 10s
|
setInterval(loadGrokStats, 10000);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1924,3 +1924,235 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Notification dropdown ──────────────────────────── */
|
||||||
|
.mc-notif-btn {
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.mc-notif-menu {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
min-width: 280px;
|
||||||
|
max-height: 350px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.mc-notif-list {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
.mc-notif-empty {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.mc-notif-item {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.mc-notif-title {
|
||||||
|
color: var(--text-bright);
|
||||||
|
}
|
||||||
|
.mc-notif-ts {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
.notif-badge {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--red);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.notif-badge.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Mobile notification button ─────────────────────── */
|
||||||
|
.mc-mobile-notif-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
padding: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Index page: sovereignty score ───────────────────── */
|
||||||
|
.sov-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.sov-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.sov-score-value {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.sov-score-label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.sov-score-detail {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.sov-bar-track {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.sov-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Index page: section headings ────────────────────── */
|
||||||
|
.mc-section-heading {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mc-section-gap {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Index page: grok mode ───────────────────────────── */
|
||||||
|
.mc-card-spaced {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.grok-badge-standby {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
.grok-toggle-box {
|
||||||
|
border: 2px solid #666;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
.grok-toggle-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.grok-toggle-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.grok-toggle-desc {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.grok-activate-btn {
|
||||||
|
background: #666;
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.grok-active .grok-toggle-box {
|
||||||
|
border-color: #00ff88;
|
||||||
|
}
|
||||||
|
.grok-active .grok-toggle-title {
|
||||||
|
color: #00ff88;
|
||||||
|
}
|
||||||
|
.grok-active .grok-activate-btn {
|
||||||
|
background: #00ff88;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Index page: heartbeat ───────────────────────────── */
|
||||||
|
.hb-log-wrap {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.hb-log {
|
||||||
|
height: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.hb-log-placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Index page: chat history ────────────────────────── */
|
||||||
|
.chat-history-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.chat-history-msg {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.chat-history-msg--user {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
.chat-history-placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.chat-history-fallback {
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.chat-history-fallback-sub {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Index page: dependency cards (JS-built) ─────────── */
|
||||||
|
.dep-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.dep-card-details {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.dep-card-score {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Heartbeat log entries ───────────────────────────── */
|
||||||
|
.hb-entry {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.hb-entry-ts {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.hb-entry-ok {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Grok badge states ───────────────────────────────── */
|
||||||
|
.grok-badge-active {
|
||||||
|
background: #00ff88;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Loading placeholder (shared) ────────────────────── */
|
||||||
|
.mc-loading-placeholder {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,6 +187,11 @@ a:hover { color: var(--orange); }
|
|||||||
.mc-dropdown-menu .mc-test-link:hover {
|
.mc-dropdown-menu .mc-test-link:hover {
|
||||||
background: rgba(124, 58, 237, 0.12);
|
background: rgba(124, 58, 237, 0.12);
|
||||||
}
|
}
|
||||||
|
.mc-dropdown-menu .mc-test-link.active {
|
||||||
|
color: var(--orange);
|
||||||
|
border-left: 3px solid var(--orange);
|
||||||
|
background: rgba(124, 58, 237, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Mobile section labels ───────────────────────── */
|
/* ── Mobile section labels ───────────────────────── */
|
||||||
.mc-mobile-section-label {
|
.mc-mobile-section-label {
|
||||||
@@ -767,6 +772,34 @@ a:hover { color: var(--orange); }
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ════════════════════════════════════════════════════
|
||||||
|
TABLET (max-width: 1024px) — iPad / Apple Pencil
|
||||||
|
════════════════════════════════════════════════════ */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
/* Larger touch targets for nav dropdown toggles */
|
||||||
|
.mc-test-link {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
min-height: 36px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.mc-dropdown-menu .mc-test-link {
|
||||||
|
padding: 10px 16px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent iPad floating toolbar from obscuring chat input */
|
||||||
|
.mc-chat-footer {
|
||||||
|
padding-bottom: max(10px, env(safe-area-inset-bottom, 10px));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure chat input is above iPad keyboard toolbar */
|
||||||
|
.mc-main {
|
||||||
|
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════════
|
/* ════════════════════════════════════════════════════
|
||||||
MOBILE (max-width: 768px) — iPhone optimized
|
MOBILE (max-width: 768px) — iPhone optimized
|
||||||
════════════════════════════════════════════════════ */
|
════════════════════════════════════════════════════ */
|
||||||
|
|||||||
Reference in New Issue
Block a user