feat: add employee API integration and request lab component
Build and Push Docker Images / detect-changes (push) Successful in 13s
Build and Push Docker Images / build-auth-api (push) Successful in 1m19s
Build and Push Docker Images / build-employee-api (push) Successful in 1m1s
Build and Push Docker Images / build-transit-api (push) Successful in 59s
Build and Push Docker Images / build-error-log-api (push) Successful in 57s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m12s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
Build and Push Docker Images / detect-changes (push) Successful in 13s
Build and Push Docker Images / build-auth-api (push) Successful in 1m19s
Build and Push Docker Images / build-employee-api (push) Successful in 1m1s
Build and Push Docker Images / build-transit-api (push) Successful in 59s
Build and Push Docker Images / build-error-log-api (push) Successful in 57s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m12s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
- Introduced EMPLOYEE_API endpoint in api.js for employee service. - Added RequestLab component for testing API routes with a user-friendly interface. - Updated monitoring_ui documentation to reflect changes in features and authentication. - Removed unnecessary login functionality from monitoring_ui to maintain unauthenticated access. - Enhanced styles for new components and improved overall UI consistency.
This commit is contained in:
@@ -7,7 +7,8 @@ import BusLinesManager from './components/BusLinesManager'
|
||||
import FleetManager from './components/FleetManager'
|
||||
import ScheduleMaker from './components/ScheduleMaker'
|
||||
import ErrorLogViewer from './components/ErrorLogViewer'
|
||||
import { IconAlert, IconBus, IconCalendar, IconGrid, IconLink, IconLogout, IconMapPin, IconRoute } from './components/icons'
|
||||
import RequestLab from './components/RequestLab'
|
||||
import { IconAlert, IconBus, IconCalendar, IconGrid, IconLink, IconLogout, IconMapPin, IconRoute, IconTerminal } from './components/icons'
|
||||
|
||||
const SECTIONS = [
|
||||
{ id: 'overview', label: 'Overzicht', eyebrow: 'Operations', title: 'Dagelijks overzicht', desc: 'Vloot, netwerk en dienstrooster in één oogopslag.', icon: IconGrid },
|
||||
@@ -17,13 +18,14 @@ const SECTIONS = [
|
||||
{ id: 'fleet', label: 'Vloot', eyebrow: 'Vlootbeheer', title: 'Vloot', desc: 'Beheer de voertuigen die deel uitmaken van de vloot.', icon: IconBus },
|
||||
{ id: 'schedules', label: 'Diensten', eyebrow: 'Planning', title: 'Dienstenplanning', desc: 'Plan wekelijkse schema’s en vaste diensten.', icon: IconCalendar },
|
||||
{ id: 'logs', label: 'Foutmeldingen', eyebrow: 'Systeem', title: 'Centrale foutmeldingen', desc: 'Bekijk fouten die door alle diensten zijn gemeld.', icon: IconAlert },
|
||||
{ id: 'lab', label: 'API-lab', eyebrow: 'Systeem', title: 'API-testomgeving', desc: 'Voer goedgekeurde verzoeken uit en inspecteer live antwoorden.', icon: IconTerminal },
|
||||
]
|
||||
|
||||
const NAV_GROUPS = [
|
||||
{ label: 'Algemeen', items: ['overview'] },
|
||||
{ label: 'Netwerkbeheer', items: ['stops', 'lines', 'routes', 'fleet'] },
|
||||
{ label: 'Planning', items: ['schedules'] },
|
||||
{ label: 'Systeem', items: ['logs'] },
|
||||
{ label: 'Systeem', items: ['logs', 'lab'] },
|
||||
]
|
||||
|
||||
export default function App() {
|
||||
@@ -110,6 +112,7 @@ export default function App() {
|
||||
{section === 'fleet' && <FleetManager token={token} user={user} />}
|
||||
{section === 'schedules' && <ScheduleMaker token={token} user={user} />}
|
||||
{section === 'logs' && <ErrorLogViewer token={token} />}
|
||||
{section === 'lab' && <RequestLab token={token} />}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,11 +25,22 @@ const TRANSIT_API = normalizeApiBaseUrl(
|
||||
RUNTIME.TRANSIT_API || import.meta.env.VITE_TRANSIT_API,
|
||||
"https://transit.xtransit.testinstance.nl",
|
||||
);
|
||||
const EMPLOYEE_API = normalizeApiBaseUrl(
|
||||
RUNTIME.EMPLOYEE_API || import.meta.env.VITE_EMPLOYEE_API,
|
||||
"https://employees.xtransit.testinstance.nl",
|
||||
);
|
||||
const ERROR_LOG_API = normalizeApiBaseUrl(
|
||||
RUNTIME.ERROR_LOG_API || import.meta.env.VITE_ERROR_LOG_API,
|
||||
"https://error.xtransit.testinstance.nl",
|
||||
);
|
||||
|
||||
export const API_BASE = {
|
||||
auth: AUTH_API,
|
||||
employee: EMPLOYEE_API,
|
||||
transit: TRANSIT_API,
|
||||
errors: ERROR_LOG_API,
|
||||
};
|
||||
|
||||
const reportClientError = async ({ service, partofservice, error }) => {
|
||||
try {
|
||||
await fetch(`${ERROR_LOG_API}/error_log`, {
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import React, { useState } from 'react'
|
||||
import { API_BASE } from '../api'
|
||||
import { IconTerminal } from './icons'
|
||||
|
||||
const SERVICE_OPTIONS = [
|
||||
{ key: 'auth', label: 'Auth API' },
|
||||
{ key: 'employee', label: 'Employee API' },
|
||||
{ key: 'transit', label: 'Transit API' },
|
||||
{ key: 'errors', label: 'Error Log API' },
|
||||
]
|
||||
|
||||
const ROUTES = [
|
||||
{ service: 'transit', method: 'GET', path: '/health', label: 'Transit health' },
|
||||
{ service: 'transit', method: 'GET', path: '/bus/admin/stops', label: 'Haltes (admin)' },
|
||||
{ service: 'transit', method: 'GET', path: '/bus/schedules', label: 'Dienstrooster-overzicht' },
|
||||
{ service: 'transit', method: 'GET', path: '/bus/schedule/daily?date=2026-09-25', label: 'Dagelijkse dienstregeling' },
|
||||
{ service: 'transit', method: 'GET', path: '/bus/public/schedules/1/progress', label: 'Openbare ritvoortgang' },
|
||||
{ service: 'auth', method: 'GET', path: '/me', label: 'Huidige sessie' },
|
||||
{ service: 'auth', method: 'POST', path: '/login', label: 'Login (userCode + wachtwoord)' },
|
||||
{ service: 'employee', method: 'GET', path: '/health', label: 'Employee health' },
|
||||
{ service: 'errors', method: 'GET', path: '/error_log?limit=50', label: 'Recente foutmeldingen' },
|
||||
]
|
||||
|
||||
const pretty = (value) => {
|
||||
try {
|
||||
return JSON.stringify(value, null, 2)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
export default function RequestLab({ token }) {
|
||||
const [route, setRoute] = useState(ROUTES[0])
|
||||
const [body, setBody] = useState('')
|
||||
const [result, setResult] = useState(null)
|
||||
const [busy, setBusy] = useState(false)
|
||||
|
||||
const routesForService = ROUTES.filter((r) => r.service === route.service)
|
||||
|
||||
const pickService = (next) => {
|
||||
const first = ROUTES.find((r) => r.service === next)
|
||||
if (first) setRoute(first)
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
if (route.method !== 'GET' && !window.confirm('Deze aanvraag kan gegevens wijzigen. Doorgaan?')) return
|
||||
setBusy(true)
|
||||
setResult(null)
|
||||
const started = performance.now()
|
||||
try {
|
||||
const response = await fetch(`${API_BASE[route.service]}${route.path}`, {
|
||||
method: route.method,
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(body ? { 'Content-Type': 'application/json' } : {}),
|
||||
},
|
||||
body: body || undefined,
|
||||
})
|
||||
const text = await response.text()
|
||||
let parsed
|
||||
try {
|
||||
parsed = JSON.parse(text)
|
||||
} catch {
|
||||
parsed = text
|
||||
}
|
||||
setResult({
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
latency: Math.round(performance.now() - started),
|
||||
body: parsed,
|
||||
label: route.label,
|
||||
})
|
||||
} catch (err) {
|
||||
setResult({
|
||||
ok: false,
|
||||
status: 0,
|
||||
latency: Math.round(performance.now() - started),
|
||||
body: { error: err.message },
|
||||
label: route.label,
|
||||
})
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="stack">
|
||||
<section className="card">
|
||||
<div className="section-header">
|
||||
<div>
|
||||
<h2>API-testomgeving</h2>
|
||||
<p>Voer een van de goedgekeurde verzoeken uit om het live antwoord te bekijken.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<select value={route.service} onChange={(e) => pickService(e.target.value)} className="no-grow">
|
||||
{SERVICE_OPTIONS.map((s) => (
|
||||
<option key={s.key} value={s.key}>{s.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={`${route.method} ${route.path}`}
|
||||
onChange={(e) => {
|
||||
const found = routesForService.find((r) => `${r.method} ${r.path}` === e.target.value)
|
||||
if (found) setRoute(found)
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
{routesForService.map((r) => (
|
||||
<option key={`${r.method} ${r.path}`} value={`${r.method} ${r.path}`}>
|
||||
{r.method} {r.path} — {r.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<textarea
|
||||
className="lab-body flex-1"
|
||||
placeholder="Optionele request body (JSON)"
|
||||
value={body}
|
||||
onChange={(e) => setBody(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<button className="no-grow" onClick={run} disabled={busy}>
|
||||
{busy ? 'Uitvoeren…' : 'Verzoek uitvoeren'}
|
||||
</button>
|
||||
<span className="muted minw-0">
|
||||
Verzoeken gaan rechtstreeks van deze browser naar de geconfigureerde service. Schrijfverzoeken vereisen een bevestiging.
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card">
|
||||
<div className="section-header">
|
||||
<div>
|
||||
<h2>Antwoord</h2>
|
||||
<p>
|
||||
{result
|
||||
? `${result.label} · ${result.status || 'Netwerk'} · ${result.latency} ms`
|
||||
: 'Nog geen verzoek uitgevoerd.'}
|
||||
</p>
|
||||
</div>
|
||||
{result && (
|
||||
<span className={`badge ${result.ok ? 'badge-success' : 'badge-danger'}`}>
|
||||
{result.ok ? 'Succes' : 'Mislukt'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{result ? (
|
||||
<pre className="lab-response">{pretty(result.body)}</pre>
|
||||
) : (
|
||||
<div className="lab-placeholder">
|
||||
<IconTerminal size={28} />
|
||||
<p>Kies een goedgekeurde route en voer deze uit om het live antwoord te inspecteren.</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -94,4 +94,11 @@ export const IconClock = (p) => (
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 7v5l3 2" />
|
||||
</Svg>
|
||||
)
|
||||
|
||||
export const IconTerminal = (p) => (
|
||||
<Svg {...p}>
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||
<path d="m7 9 3 3-3 3M13 15h4" />
|
||||
</Svg>
|
||||
)
|
||||
@@ -840,6 +840,57 @@ button.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Request lab
|
||||
============================================================ */
|
||||
|
||||
.lab-body {
|
||||
min-height: 90px;
|
||||
resize: vertical;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85rem;
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-xs);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.lab-body:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
|
||||
}
|
||||
|
||||
.lab-response {
|
||||
margin: 4px 0 0;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
max-height: 480px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.lab-placeholder {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
min-height: 220px;
|
||||
color: var(--text-faint);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.lab-placeholder p {
|
||||
margin: 0;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Route stop board (drag & drop)
|
||||
============================================================ */
|
||||
|
||||
Reference in New Issue
Block a user