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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user