- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark, Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI) - aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource, an aether-core gekoppelt (Rechte über aether_users.admin_group), mit WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU) - Prototypen admin/ vervollständigt - SQL-Schema aether_admin.sql - Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
171 lines
6.3 KiB
Lua
171 lines
6.3 KiB
Lua
-- =====================================================================
|
|
-- aether-police / Modul: Dashboard
|
|
--
|
|
-- Zeigt eine Lageübersicht. Alle Zahlen werden bei jedem Aufruf frisch
|
|
-- aus der Datenbank bzw. dem Dienstsystem geholt — nichts ist fest
|
|
-- programmiert. Das Dashboard bewertet nichts, es zeigt nur an.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereModul({
|
|
id = 'dashboard', name = 'Übersicht', icon = '▤',
|
|
reihenfolge = 1,
|
|
beschreibung = 'Lageübersicht der Dienststelle',
|
|
})
|
|
|
|
--- Sammelt alle Kennzahlen für die Übersicht
|
|
Kern.RegistriereAktion('dashboard', 'laden', nil, function(quelle, beamter)
|
|
local charakterId = beamter.charakterId
|
|
|
|
-- --- Beamte im Dienst ---
|
|
local dienstliste = Dienst.BaueListe()
|
|
local nachStatus = {}
|
|
for _, eintrag in ipairs(dienstliste) do
|
|
nachStatus[eintrag.status] = (nachStatus[eintrag.status] or 0) + 1
|
|
end
|
|
|
|
-- --- Offene Einsätze ---
|
|
local einsaetze = MySQL.query.await([[
|
|
SELECT id, nummer, titel, kategorie, prioritaet, ort, status, erstellt_am,
|
|
pos_x, pos_y, pos_z
|
|
FROM police_einsatz
|
|
WHERE status <> 'abgeschlossen'
|
|
ORDER BY prioritaet ASC, id DESC
|
|
LIMIT 20
|
|
]]) or {}
|
|
|
|
-- --- Offene Anzeigen (Entwürfe warten auf Bestätigung) ---
|
|
local anzeigenOffen = MySQL.scalar.await(
|
|
"SELECT COUNT(*) FROM police_anzeige WHERE status = 'entwurf'") or 0
|
|
|
|
-- --- Aktive Fahndungen ---
|
|
local fahndungen = MySQL.query.await([[
|
|
SELECT id, typ, bezeichnung, grund, gefahr, prioritaet
|
|
FROM police_fahndung
|
|
WHERE aktiv = 1
|
|
ORDER BY prioritaet ASC, id DESC
|
|
LIMIT 10
|
|
]]) or {}
|
|
|
|
-- --- Gefangene und auslaufende Haftzeiten ---
|
|
local gewahrsam = MySQL.query.await([[
|
|
SELECT g.id, g.character, g.grund, g.faellig_am, g.zelle,
|
|
TIMESTAMPDIFF(MINUTE, NOW(), g.faellig_am) AS rest,
|
|
c.firstname, c.lastname
|
|
FROM police_gewahrsam g
|
|
JOIN aether_characters c ON c.id = g.character
|
|
WHERE g.entlassen_am IS NULL
|
|
ORDER BY g.faellig_am ASC
|
|
]]) or {}
|
|
|
|
local abgelaufen = 0
|
|
for _, eintrag in ipairs(gewahrsam) do
|
|
if (tonumber(eintrag.rest) or 0) <= 0 then abgelaufen = abgelaufen + 1 end
|
|
end
|
|
|
|
-- --- Offene Berichte des eigenen Beamten ---
|
|
local eigeneBerichte = MySQL.query.await([[
|
|
SELECT id, nummer, titel, status, erstellt_am
|
|
FROM police_bericht
|
|
WHERE erstellt_von = ? AND status = 'entwurf'
|
|
ORDER BY id DESC LIMIT 10
|
|
]], { charakterId }) or {}
|
|
|
|
-- --- Akten, die auf Freigabe warten ---
|
|
local akteFreigabe = MySQL.scalar.await(
|
|
"SELECT COUNT(*) FROM police_akte WHERE status = 'pruefung'") or 0
|
|
|
|
-- --- Dienstfahrzeuge im Einsatz ---
|
|
local fahrzeuge = MySQL.query.await([[
|
|
SELECT id, kennzeichen, bezeichnung, status, wartung, tank, zugewiesen_an
|
|
FROM police_fahrzeug
|
|
ORDER BY kennzeichen ASC
|
|
]]) or {}
|
|
|
|
local fahrzeugeImEinsatz = 0
|
|
local wartungFaellig = 0
|
|
for _, fahrzeug in ipairs(fahrzeuge) do
|
|
if fahrzeug.status ~= 'verfuegbar' then fahrzeugeImEinsatz = fahrzeugeImEinsatz + 1 end
|
|
if fahrzeug.wartung ~= 'ok' then wartungFaellig = wartungFaellig + 1 end
|
|
end
|
|
|
|
-- --- Neueste Mitteilungen ---
|
|
local mitteilungen = MySQL.query.await([[
|
|
SELECT n.id, n.betreff, n.text, n.gesendet, c.firstname, c.lastname
|
|
FROM police_nachricht n
|
|
LEFT JOIN aether_characters c ON c.id = n.absender
|
|
WHERE n.empf_typ = 'rundmeldung'
|
|
OR (n.empf_typ = 'person' AND n.empfaenger = ?)
|
|
OR (n.empf_typ = 'abteilung' AND n.empfaenger = ?)
|
|
ORDER BY n.id DESC LIMIT 5
|
|
]], { tostring(charakterId), beamter.abteilung }) or {}
|
|
|
|
-- --- Warnungen zusammenstellen (reine Hinweise) ---
|
|
local warnungen = {}
|
|
if abgelaufen > 0 then
|
|
warnungen[#warnungen + 1] = {
|
|
prioritaet = 'hoch',
|
|
text = ('%d Haftzeit%s abgelaufen — Entlassung muss manuell entschieden werden.')
|
|
:format(abgelaufen, abgelaufen == 1 and '' or 'en'),
|
|
zielTyp = 'gewahrsam',
|
|
}
|
|
end
|
|
if anzeigenOffen > 0 then
|
|
warnungen[#warnungen + 1] = {
|
|
prioritaet = 'normal',
|
|
text = ('%d Anzeige%s im Entwurf — wartet auf Bestätigung.')
|
|
:format(anzeigenOffen, anzeigenOffen == 1 and '' or 'n'),
|
|
zielTyp = 'anzeigen',
|
|
}
|
|
end
|
|
if akteFreigabe > 0 then
|
|
warnungen[#warnungen + 1] = {
|
|
prioritaet = 'normal',
|
|
text = ('%d Akte%s wartet auf Freigabe.')
|
|
:format(akteFreigabe, akteFreigabe == 1 and '' or 'n'),
|
|
zielTyp = 'akten',
|
|
}
|
|
end
|
|
if wartungFaellig > 0 then
|
|
warnungen[#warnungen + 1] = {
|
|
prioritaet = 'niedrig',
|
|
text = ('%d Dienstfahrzeug%s benötigt Wartung.')
|
|
:format(wartungFaellig, wartungFaellig == 1 and '' or 'e'),
|
|
zielTyp = 'fahrzeuge',
|
|
}
|
|
end
|
|
|
|
return {
|
|
kennzahlen = {
|
|
imDienst = #dienstliste,
|
|
verfuegbar = nachStatus.verfuegbar or 0,
|
|
imEinsatz = nachStatus.einsatz or 0,
|
|
pause = nachStatus.pause or 0,
|
|
einsaetzeOffen = #einsaetze,
|
|
anzeigenOffen = anzeigenOffen,
|
|
fahndungen = #fahndungen,
|
|
gefangene = #gewahrsam,
|
|
haftAbgelaufen = abgelaufen,
|
|
berichteOffen = #eigeneBerichte,
|
|
akteFreigabe = akteFreigabe,
|
|
fahrzeugeGesamt = #fahrzeuge,
|
|
fahrzeugeEinsatz= fahrzeugeImEinsatz,
|
|
},
|
|
beamte = dienstliste,
|
|
einsaetze = einsaetze,
|
|
fahndungen = fahndungen,
|
|
gewahrsam = gewahrsam,
|
|
berichte = eigeneBerichte,
|
|
fahrzeuge = fahrzeuge,
|
|
mitteilungen = mitteilungen,
|
|
warnungen = warnungen,
|
|
hinweise = Hinweise.Lade(charakterId, 15),
|
|
}
|
|
end)
|
|
|
|
--- Markiert einen Hinweis als gelesen
|
|
Kern.RegistriereAktion('dashboard', 'hinweisGelesen', nil, function(quelle, beamter, daten)
|
|
if type(daten.id) ~= 'number' then return nil, 'Ungültige Kennung.' end
|
|
Hinweise.MarkiereGelesen(beamter.charakterId, daten.id)
|
|
return { ok = true }
|
|
end)
|