From e1cc971ccaae43978f0298297648c32eadae1750 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Sat, 3 Jan 2026 18:31:16 -0600 Subject: [PATCH] feat(DebugLogsPage): update log management with search, filtering, and pagination features --- .../components/debug/DebugLogsPage.vue | 204 +++++++++++++++--- 1 file changed, 178 insertions(+), 26 deletions(-) diff --git a/meshchatx/src/frontend/components/debug/DebugLogsPage.vue b/meshchatx/src/frontend/components/debug/DebugLogsPage.vue index 83abf86..8382ba6 100644 --- a/meshchatx/src/frontend/components/debug/DebugLogsPage.vue +++ b/meshchatx/src/frontend/components/debug/DebugLogsPage.vue @@ -3,32 +3,73 @@ class="flex flex-col flex-1 overflow-hidden min-w-0 bg-gradient-to-br from-slate-50 via-slate-100 to-white dark:from-zinc-950 dark:via-zinc-900 dark:to-zinc-900" >
-
-
-
Diagnostics
-
Debug Logs
+
+
+
+
Diagnostics
+
Debug Logs
+
+
+ + +
-
- - + +
+
+
+ +
+ +
+ + + +
-
-
+
+
- No logs collected yet. + {{ loading ? 'Loading logs...' : 'No logs found matching your criteria.' }}
{{ formatTime(log.timestamp) }} {{ @@ -37,7 +78,64 @@ [{{ log.module }}] - {{ log.message }} + + {{ log.message }} + + + {{ log.anomaly_type || 'anomaly' }} + + +
+
+ + +
+
+ + +
+
@@ -57,33 +155,80 @@ export default { data() { return { logs: [], + total: 0, + limit: 100, + offset: 0, + search: "", + level: "", + is_anomaly: false, + loading: false, updateInterval: null, + searchTimeout: null, }; }, mounted() { this.refreshLogs(); this.updateInterval = setInterval(() => { - this.refreshLogs(); + // Only auto-refresh if on page 1 and no search + if (this.offset === 0 && !this.search && !this.is_anomaly && !this.level) { + this.refreshLogs(true); + } }, 5000); }, beforeUnmount() { if (this.updateInterval) { clearInterval(this.updateInterval); } + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } }, methods: { - async refreshLogs() { + async refreshLogs(silent = false) { + if (!silent) this.loading = true; try { - const response = await window.axios.get("/api/v1/debug/logs"); - this.logs = response.data; + const params = { + limit: this.limit, + offset: this.offset, + search: this.search || undefined, + level: this.level || undefined, + is_anomaly: this.is_anomaly ? true : undefined, + }; + const response = await window.axios.get("/api/v1/debug/logs", { params }); + this.logs = response.data.logs; + this.total = response.data.total; } catch (e) { console.log("Failed to fetch logs", e); + if (!silent) ToastUtils.error("Failed to fetch logs"); + } finally { + if (!silent) this.loading = false; + } + }, + debouncedSearch() { + if (this.searchTimeout) clearTimeout(this.searchTimeout); + this.searchTimeout = setTimeout(() => { + this.offset = 0; + this.refreshLogs(); + }, 500); + }, + prevPage() { + if (this.offset >= this.limit) { + this.offset -= this.limit; + this.refreshLogs(); + } + }, + nextPage() { + if (this.offset + this.limit < this.total) { + this.offset += this.limit; + this.refreshLogs(); } }, formatTime(timestamp) { try { - const date = new Date(timestamp); - return date.toLocaleTimeString(); + // If timestamp is a number (Unix timestamp from Python), multiply by 1000 for JS + const ts = typeof timestamp === 'number' ? timestamp * 1000 : timestamp; + const date = new Date(ts); + return date.toLocaleString(); } catch { return timestamp; } @@ -97,10 +242,10 @@ export default { return "text-gray-400"; }, async copyLogs() { - const logText = this.logs.map((l) => `${l.timestamp} [${l.level}] [${l.module}] ${l.message}`).join("\n"); + const logText = this.logs.map((l) => `${this.formatTime(l.timestamp)} [${l.level}] [${l.module}] ${l.message}${l.is_anomaly ? ' [ANOMALY:' + l.anomaly_type + ']' : ''}`).join("\n"); try { await navigator.clipboard.writeText(logText); - ToastUtils.success("Logs copied to clipboard"); + ToastUtils.success("Logs on this page copied to clipboard"); } catch { ToastUtils.error("Failed to copy logs"); } @@ -108,3 +253,10 @@ export default { }, }; + +