Massive update
This commit is contained in:
@@ -55,4 +55,38 @@ export const c2api = {
|
||||
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
||||
return request<unknown[]>(`/calls${qs}`);
|
||||
},
|
||||
|
||||
// Incidents
|
||||
getIncidents: (params?: { status?: string; type?: string }) => {
|
||||
const qs = params ? "?" + new URLSearchParams(params as Record<string, string>).toString() : "";
|
||||
return request<unknown[]>(`/incidents${qs}`);
|
||||
},
|
||||
getIncident: (id: string) => request<unknown>(`/incidents/${id}`),
|
||||
createIncident: (body: object) =>
|
||||
request("/incidents", { method: "POST", body: JSON.stringify(body) }),
|
||||
updateIncident: (id: string, body: object) =>
|
||||
request(`/incidents/${id}`, { method: "PUT", body: JSON.stringify(body) }),
|
||||
deleteIncident: (id: string) =>
|
||||
request(`/incidents/${id}`, { method: "DELETE" }),
|
||||
linkCallToIncident: (incidentId: string, callId: string) =>
|
||||
request(`/incidents/${incidentId}/calls/${callId}`, { method: "POST" }),
|
||||
|
||||
// Alerts
|
||||
getAlerts: (acknowledged?: boolean) => {
|
||||
const qs = acknowledged !== undefined ? `?acknowledged=${acknowledged}` : "";
|
||||
return request<unknown[]>(`/alerts${qs}`);
|
||||
},
|
||||
acknowledgeAlert: (id: string) =>
|
||||
request(`/alerts/${id}/acknowledge`, { method: "POST" }),
|
||||
getAlertRules: () => request<unknown[]>("/alert-rules"),
|
||||
createAlertRule: (body: object) =>
|
||||
request("/alert-rules", { method: "POST", body: JSON.stringify(body) }),
|
||||
updateAlertRule: (id: string, body: object) =>
|
||||
request(`/alert-rules/${id}`, { method: "PUT", body: JSON.stringify(body) }),
|
||||
deleteAlertRule: (id: string) =>
|
||||
request(`/alert-rules/${id}`, { method: "DELETE" }),
|
||||
|
||||
// Node key management
|
||||
reissueNodeKey: (nodeId: string) =>
|
||||
request(`/nodes/${nodeId}/reissue-key`, { method: "POST" }),
|
||||
};
|
||||
|
||||
@@ -49,3 +49,27 @@ export interface IncidentRecord {
|
||||
summary: string | null;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface AlertRule {
|
||||
rule_id: string;
|
||||
name: string;
|
||||
keywords: string[];
|
||||
talkgroup_ids: number[];
|
||||
enabled: boolean;
|
||||
discord_webhook: string | null;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface AlertEvent {
|
||||
alert_id: string;
|
||||
rule_id: string;
|
||||
rule_name: string;
|
||||
call_id: string;
|
||||
node_id: string;
|
||||
talkgroup_id: number | null;
|
||||
talkgroup_name: string | null;
|
||||
matched_keywords: string[];
|
||||
transcript_snippet: string | null;
|
||||
triggered_at: string;
|
||||
acknowledged: boolean;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { collection, onSnapshot, query, orderBy, limit, where, FirestoreError } from "firebase/firestore";
|
||||
import { onAuthStateChanged } from "firebase/auth";
|
||||
import { db, auth } from "@/lib/firebase";
|
||||
import type { AlertEvent } from "@/lib/types";
|
||||
|
||||
const toISO = (v: unknown): string =>
|
||||
(v as { toDate?: () => Date })?.toDate?.()?.toISOString?.() ??
|
||||
(typeof v === "string" ? v : new Date().toISOString());
|
||||
|
||||
export function useAlerts(limitCount = 50) {
|
||||
const [alerts, setAlerts] = useState<AlertEvent[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let unsubFirestore: (() => void) | undefined;
|
||||
|
||||
const unsubAuth = onAuthStateChanged(auth, (user) => {
|
||||
if (unsubFirestore) { unsubFirestore(); unsubFirestore = undefined; }
|
||||
|
||||
if (!user) {
|
||||
setAlerts([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const q = query(
|
||||
collection(db, "alert_events"),
|
||||
orderBy("triggered_at", "desc"),
|
||||
limit(limitCount)
|
||||
);
|
||||
unsubFirestore = onSnapshot(q, (snap) => {
|
||||
setAlerts(snap.docs.map((d) => {
|
||||
const data = d.data();
|
||||
return {
|
||||
...data,
|
||||
triggered_at: toISO(data.triggered_at),
|
||||
} as AlertEvent;
|
||||
}));
|
||||
setLoading(false);
|
||||
}, (err: FirestoreError) => {
|
||||
console.error("useAlerts:", err);
|
||||
setError(err.message);
|
||||
setLoading(false);
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubAuth();
|
||||
if (unsubFirestore) unsubFirestore();
|
||||
};
|
||||
}, [limitCount]);
|
||||
|
||||
return { alerts, loading, error };
|
||||
}
|
||||
|
||||
export function useUnacknowledgedAlerts() {
|
||||
const [alerts, setAlerts] = useState<AlertEvent[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let unsubFirestore: (() => void) | undefined;
|
||||
|
||||
const unsubAuth = onAuthStateChanged(auth, (user) => {
|
||||
if (unsubFirestore) { unsubFirestore(); unsubFirestore = undefined; }
|
||||
|
||||
if (!user) {
|
||||
setAlerts([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const q = query(
|
||||
collection(db, "alert_events"),
|
||||
where("acknowledged", "==", false),
|
||||
orderBy("triggered_at", "desc"),
|
||||
limit(100)
|
||||
);
|
||||
unsubFirestore = onSnapshot(q, (snap) => {
|
||||
setAlerts(snap.docs.map((d) => {
|
||||
const data = d.data();
|
||||
return { ...data, triggered_at: toISO(data.triggered_at) } as AlertEvent;
|
||||
}));
|
||||
}, (err: FirestoreError) => { console.error("useUnacknowledgedAlerts:", err); });
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubAuth();
|
||||
if (unsubFirestore) unsubFirestore();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return alerts;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { collection, onSnapshot, query, orderBy, limit, where, FirestoreError } from "firebase/firestore";
|
||||
import { onAuthStateChanged } from "firebase/auth";
|
||||
import { db, auth } from "@/lib/firebase";
|
||||
import type { IncidentRecord } from "@/lib/types";
|
||||
|
||||
const toISO = (v: unknown): string =>
|
||||
(v as { toDate?: () => Date })?.toDate?.()?.toISOString?.() ??
|
||||
(typeof v === "string" ? v : new Date().toISOString());
|
||||
|
||||
export function useIncidents(limitCount = 100) {
|
||||
const [incidents, setIncidents] = useState<IncidentRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let unsubFirestore: (() => void) | undefined;
|
||||
|
||||
const unsubAuth = onAuthStateChanged(auth, (user) => {
|
||||
if (unsubFirestore) { unsubFirestore(); unsubFirestore = undefined; }
|
||||
|
||||
if (!user) {
|
||||
setIncidents([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const q = query(
|
||||
collection(db, "incidents"),
|
||||
orderBy("started_at", "desc"),
|
||||
limit(limitCount)
|
||||
);
|
||||
unsubFirestore = onSnapshot(q, (snap) => {
|
||||
setIncidents(snap.docs.map((d) => {
|
||||
const data = d.data();
|
||||
return {
|
||||
...data,
|
||||
started_at: toISO(data.started_at),
|
||||
updated_at: toISO(data.updated_at),
|
||||
} as IncidentRecord;
|
||||
}));
|
||||
setLoading(false);
|
||||
}, (err: FirestoreError) => {
|
||||
console.error("useIncidents:", err);
|
||||
setError(err.message);
|
||||
setLoading(false);
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubAuth();
|
||||
if (unsubFirestore) unsubFirestore();
|
||||
};
|
||||
}, [limitCount]);
|
||||
|
||||
return { incidents, loading, error };
|
||||
}
|
||||
|
||||
export function useActiveIncidents() {
|
||||
const [incidents, setIncidents] = useState<IncidentRecord[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let unsubFirestore: (() => void) | undefined;
|
||||
|
||||
const unsubAuth = onAuthStateChanged(auth, (user) => {
|
||||
if (unsubFirestore) { unsubFirestore(); unsubFirestore = undefined; }
|
||||
|
||||
if (!user) {
|
||||
setIncidents([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const q = query(collection(db, "incidents"), where("status", "==", "active"));
|
||||
unsubFirestore = onSnapshot(q, (snap) => {
|
||||
setIncidents(snap.docs.map((d) => {
|
||||
const data = d.data();
|
||||
return {
|
||||
...data,
|
||||
started_at: toISO(data.started_at),
|
||||
updated_at: toISO(data.updated_at),
|
||||
} as IncidentRecord;
|
||||
}));
|
||||
}, (err: FirestoreError) => { console.error("useActiveIncidents:", err); });
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubAuth();
|
||||
if (unsubFirestore) unsubFirestore();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return incidents;
|
||||
}
|
||||
Reference in New Issue
Block a user