feat: add centralized error logging system

- Implemented Error Log API to capture and store error logs from various services.
- Updated admin_ui to include Error Log Viewer component for inspecting logs.
- Enhanced error handling in auth_api, employee_api, and transit_api to report errors to the Error Log API.
- Modified existing services to utilize the new error logging functionality.
- Updated Docker Compose configuration to include the Error Log API service.
- Added error logging service in bus_terminal for Flutter application.
This commit is contained in:
Sven laptop
2026-05-17 23:58:23 +02:00
parent 73de0d713e
commit 0e19aa0521
31 changed files with 688 additions and 34 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ export default function BusLinesManager({ token, user }) {
const [estimatedDurationMinutes, setEstimatedDurationMinutes] = useState('')
const [description, setDescription] = useState('')
const [message, setMessage] = useState(null)
const [isAdmin] = useState(Boolean(user?.userCode))
const [isAdmin] = useState(Boolean(user?.isAdmin))
const [deleteConfirm, setDeleteConfirm] = useState(null)
const [editingLine, setEditingLine] = useState(null)
@@ -0,0 +1,74 @@
import React, { useEffect, useMemo, useState } from 'react'
import { errorLogClient } from '../api'
const formatDate = (value) => {
if (!value) return '—'
try {
return new Date(value).toLocaleString()
} catch (_) {
return String(value)
}
}
export default function ErrorLogViewer({ token }) {
const client = useMemo(() => errorLogClient(token), [token])
const [logs, setLogs] = useState([])
const [service, setService] = useState('')
const [partOfService, setPartOfService] = useState('')
const [loading, setLoading] = useState(false)
const [message, setMessage] = useState(null)
const loadLogs = async () => {
setLoading(true)
setMessage(null)
try {
const params = { limit: 100 }
if (service) params.service = service
if (partOfService) params.partOfService = partOfService
const res = await client.get('/error_log', { params })
setLogs(res.data.errors || [])
} catch (err) {
setMessage(err.response?.data?.error || err.message || 'Kon fouten niet laden')
} finally {
setLoading(false)
}
}
useEffect(() => {
loadLogs()
}, [service, partOfService])
return (
<section className="card">
<div className="section-header">
<div>
<h2>Error log</h2>
<p>Laatste foutmeldingen uit de centrale logging service.</p>
</div>
<button className="secondary" onClick={loadLogs} disabled={loading}>
{loading ? 'Laden...' : 'Ververs'}
</button>
</div>
<div className="form-row">
<input placeholder="Service filter" value={service} onChange={e => setService(e.target.value)} />
<input placeholder="Part of service filter" value={partOfService} onChange={e => setPartOfService(e.target.value)} />
</div>
{message && <div className="message">{message}</div>}
<div className="list-grid">
{logs.map(log => (
<article className="mini-card" key={log.id}>
<strong>{log.service}</strong>
<div>{formatDate(log.date)}</div>
<div className="muted" style={{ marginTop: 6 }}>{log.partOfService || '—'}</div>
<div style={{ marginTop: 8 }}>{log.error}</div>
</article>
))}
</div>
{!logs.length && !message && <div className="muted">Nog geen foutmeldingen gevonden.</div>}
</section>
)
}
@@ -172,7 +172,7 @@ const FixedScheduleEditor = ({
const [availableDrivers, setAvailableDrivers] = useState([])
const [pendingBatchData, setPendingBatchData] = useState(null)
const isAdmin = Boolean(user?.userCode)
const isAdmin = Boolean(user?.isAdmin)
const resetForm = () => {
setEditingScheduleId(null)
+1 -1
View File
@@ -9,7 +9,7 @@ export default function FleetManager({ token, user }) {
const [licensePlate, setLicensePlate] = useState('')
const [message, setMessage] = useState(null)
const [canCreateBus, setCanCreateBus] = useState(true)
const [isAdmin] = useState(Boolean(user?.userCode))
const [isAdmin] = useState(Boolean(user?.isAdmin))
const [editingBus, setEditingBus] = useState(null)
const [deleteConfirm, setDeleteConfirm] = useState(null)
+1 -1
View File
@@ -13,7 +13,7 @@ export default function LinkStop({ token, user }) {
const [draggedItem, setDraggedItem] = useState(null)
const [deleteConfirm, setDeleteConfirm] = useState(null)
const isAdmin = Boolean(user?.userCode)
const isAdmin = Boolean(user?.isAdmin)
const nextOrder = useMemo(() => stops.length + 1, [stops])
+1 -1
View File
@@ -11,7 +11,7 @@ export default function ScheduleMaker({ token, user }) {
const [drivers, setDrivers] = useState([])
const [selectedDriverFilter, setSelectedDriverFilter] = useState('')
const [message, setMessage] = useState(null)
const [isAdmin] = useState(Boolean(user?.userCode))
const [isAdmin] = useState(Boolean(user?.isAdmin))
const [overviewDate, setOverviewDate] = useState(new Date().toISOString().split('T')[0])
const loadScheduleTemplates = async () => {
+1 -1
View File
@@ -8,7 +8,7 @@ export default function StopsManager({ token, user }) {
const [lat, setLat] = useState('')
const [lon, setLon] = useState('')
const [message, setMessage] = useState(null)
const [isAdmin] = useState(Boolean(user?.userCode))
const [isAdmin] = useState(Boolean(user?.isAdmin))
const [deleteConfirm, setDeleteConfirm] = useState(null)
const [editingStop, setEditingStop] = useState(null)