refactor: 🎨 update dashboard activity data structure to use timestamps and add local hour bucketing

This commit is contained in:
2026-07-21 11:05:17 +01:00
parent 5b606404fc
commit 1b3404155c
4 changed files with 21 additions and 9 deletions
+7 -7
View File
@@ -2925,14 +2925,14 @@ def api_dashboard():
}) })
cursor.execute(''' cursor.execute('''
SELECT HOUR(timestamp) AS hour, COUNT(*) AS count SELECT timestamp FROM AuditLog
FROM AuditLog WHERE timestamp >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 24 HOUR)
WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
GROUP BY HOUR(timestamp)
ORDER BY hour
''') ''')
activity_by_hour = {row['hour']: row['count'] for row in cursor.fetchall()} activity = [
activity = [{'hour': h, 'count': activity_by_hour.get(h, 0)} for h in range(24)] row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')
if hasattr(row['timestamp'], 'strftime') else str(row['timestamp'])
for row in cursor.fetchall()
]
return jsonify({ return jsonify({
'stats': { 'stats': {
+1 -1
View File
@@ -213,7 +213,7 @@ export const api = {
available: number; available: number;
status: "active" | "alerting"; status: "active" | "alerting";
}[]; }[];
activity: { hour: number; count: number }[]; activity: string[];
}>(await fetchApi("/api/v2/dashboard")); }>(await fetchApi("/api/v2/dashboard"));
}, },
async search(q: string) { async search(q: string) {
+11
View File
@@ -21,3 +21,14 @@ export function formatLocalTime(ts?: string | null, fallback = "—"): string {
if (!d) return ts?.trim() || fallback; if (!d) return ts?.trim() || fallback;
return d.toLocaleString(); return d.toLocaleString();
} }
/** Bucket audit timestamps from the last 24h by local hour of day (023). */
export function bucketActivityByLocalHour(timestamps: string[]): { hour: number; count: number }[] {
const counts = new Array<number>(24).fill(0);
for (const ts of timestamps) {
const d = parseApiTimestamp(ts);
if (!d) continue;
counts[d.getHours()]++;
}
return counts.map((count, hour) => ({ hour, count }));
}
+2 -1
View File
@@ -3,6 +3,7 @@ import { ref, onMounted, computed } from "vue";
import { RouterLink } from "vue-router"; import { RouterLink } from "vue-router";
import { Network, Wifi, Layers, Server } from "lucide-vue-next"; import { Network, Wifi, Layers, Server } from "lucide-vue-next";
import { api } from "@/api"; import { api } from "@/api";
import { bucketActivityByLocalHour } from "@/utils/datetime";
interface DashboardStats { interface DashboardStats {
total_ips: number; total_ips: number;
@@ -46,7 +47,7 @@ onMounted(async () => {
const d = await api.dashboard(); const d = await api.dashboard();
stats.value = d.stats; stats.value = d.stats;
subnetOverview.value = d.subnet_overview; subnetOverview.value = d.subnet_overview;
activity.value = d.activity; activity.value = bucketActivityByLocalHour(d.activity);
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : "Failed to load dashboard"; error.value = e instanceof Error ? e.message : "Failed to load dashboard";
} finally { } finally {