V2.1.1 #58
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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 (0–23). */
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user