+
+
- 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' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Showing
+ {{ total === 0 ? 0 : offset + 1 }}
+ to
+ {{ Math.min(offset + limit, total) }}
+ of
+ {{ total }}
+ results
+
+
+
+
+
+
@@ -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 {
},
};
+
+