Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 82 additions & 3 deletions dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -10110,6 +10110,34 @@ def get_local_ip():
</div><!-- end page-nemoclaw (theme 2) -->


<div class="page" id="page-logs">
<div class="refresh-bar" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<button class="refresh-btn" onclick="loadLogs()">&#8635; Refresh</button>
<label style="font-size:12px;color:var(--text-secondary);">Lines:
<input id="log-lines" type="number" value="200" min="10" max="2000" step="10"
style="width:60px;margin-left:4px;padding:3px 6px;border:1px solid var(--border-primary);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);font-size:12px;">
</label>
<input id="log-filter" type="text" placeholder="Filter logs…" oninput="filterLogLines()"
style="padding:5px 10px;border-radius:7px;border:1px solid var(--border-primary);background:var(--bg-secondary);color:var(--text-primary);font-size:12px;width:180px;">
<div style="display:flex;gap:4px;flex-wrap:wrap;">
<button class="time-btn active" id="log-filter-all" onclick="setLogLevel('all',this)">All</button>
<button class="time-btn" id="log-filter-info" onclick="setLogLevel('info',this)">Info</button>
<button class="time-btn" id="log-filter-warn" onclick="setLogLevel('warn',this)">Warn</button>
<button class="time-btn" id="log-filter-error" onclick="setLogLevel('error',this)">Error</button>
</div>
<label style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);cursor:pointer;margin-left:auto;">
<input type="checkbox" id="log-autoscroll" checked> Auto-scroll
</label>
<span id="log-stream-status" style="font-size:11px;color:var(--text-muted);">&#9679; Connecting…</span>
</div>
<div class="card" style="padding:0;overflow:hidden;margin-top:8px;">
<div id="logs-full"
style="font-family:monospace;font-size:12px;padding:12px 16px;max-height:calc(100vh - 180px);overflow-y:auto;background:var(--bg-secondary);border-radius:8px;">
<div style="color:var(--text-muted);text-align:center;padding:24px;">Loading logs…</div>
</div>
</div>
</div>

<script>
// === Budget & Alert Functions ===
function openBudgetModal() {
Expand Down Expand Up @@ -14574,17 +14602,25 @@ def get_local_ip():
if (window.CLOUD_MODE) return;
if (logStream) logStream.close();
streamBuffer = [];
var statusEl = document.getElementById('log-stream-status');
if (statusEl) statusEl.textContent = '\u25cf Connecting\u2026';
logStream = new EventSource('/api/logs-stream' + (localStorage.getItem('clawmetry-token') ? '?token=' + encodeURIComponent(localStorage.getItem('clawmetry-token')) : ''));
logStream.onopen = function() {
var s = document.getElementById('log-stream-status');
if (s) { s.textContent = '\u25cf Live'; s.style.color = '#22c55e'; }
};
logStream.onmessage = function(e) {
var data = JSON.parse(e.data);
streamBuffer.push(data.line);
if (streamBuffer.length > MAX_STREAM_LINES) streamBuffer.shift();
appendLogLine('ov-logs', data.line);
appendLogLine('logs-full', data.line);
processFlowEvent(data.line);
document.getElementById('refresh-time').textContent = 'Live ' + new Date().toLocaleTimeString();
document.getElementById('refresh-time').textContent = 'Live \u2022 ' + new Date().toLocaleTimeString();
};
logStream.onerror = function() {
var s = document.getElementById('log-stream-status');
if (s) { s.textContent = '\u25cf Reconnecting\u2026'; s.style.color = '#f59e0b'; }
setTimeout(startLogStream, 5000);
};
}
Expand Down Expand Up @@ -14632,11 +14668,54 @@ def get_local_ip():
div.innerHTML = '<span class="' + parsed.cls + '">' + parsed.html + '</span>';
el.appendChild(div);
while (el.children.length > MAX_STREAM_LINES) el.removeChild(el.firstChild);
if (el.scrollHeight - el.scrollTop - el.clientHeight < 150) {
el.scrollTop = el.scrollHeight;
// Apply live filter for logs-full
if (elId === 'logs-full') {
var span = el.lastElementChild ? el.lastElementChild.querySelector('span') : null;
var cls = span ? span.className : '';
var text = el.lastElementChild ? el.lastElementChild.textContent.toLowerCase() : '';
var query = (document.getElementById('log-filter') ? document.getElementById('log-filter').value : '').toLowerCase();
var levelOk = _logLevelFilter === 'all'
|| (_logLevelFilter === 'error' && cls === 'err')
|| (_logLevelFilter === 'warn' && (cls === 'warn' || cls === 'err'))
|| (_logLevelFilter === 'info' && (cls === 'info' || cls === 'warn' || cls === 'err'));
if (!levelOk || (query && !text.includes(query))) el.lastElementChild.style.display = 'none';
}
var autoScroll = document.getElementById('log-autoscroll');
if (!autoScroll || autoScroll.checked) {
if (el.scrollHeight - el.scrollTop - el.clientHeight < 150) {
el.scrollTop = el.scrollHeight;
}
}
}


// ===== Log Tab Helpers =====
var _logLevelFilter = 'all';

function setLogLevel(level, btn) {
_logLevelFilter = level;
document.querySelectorAll('#page-logs .time-btn').forEach(function(b) { b.classList.remove('active'); });
if (btn) btn.classList.add('active');
filterLogLines();
}

function filterLogLines() {
var el = document.getElementById('logs-full');
if (!el) return;
var query = (document.getElementById('log-filter') ? document.getElementById('log-filter').value : '').toLowerCase();
Array.from(el.children).forEach(function(div) {
var span = div.querySelector('span');
var cls = span ? span.className : '';
var text = div.textContent.toLowerCase();
var levelOk = _logLevelFilter === 'all'
|| (_logLevelFilter === 'error' && cls === 'err')
|| (_logLevelFilter === 'warn' && (cls === 'warn' || cls === 'err'))
|| (_logLevelFilter === 'info' && (cls === 'info' || cls === 'warn' || cls === 'err'));
var queryOk = !query || text.includes(query);
div.style.display = levelOk && queryOk ? '' : 'none';
});
}

// ===== Flow Visualization Engine =====
var flowStats = { messages: 0, events: 0, activeTools: {}, msgTimestamps: [] };
var flowInitDone = false;
Expand Down
Loading