diff --git a/admin_ui/src/App.jsx b/admin_ui/src/App.jsx index 61adf5b..3ee9b45 100644 --- a/admin_ui/src/App.jsx +++ b/admin_ui/src/App.jsx @@ -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' && } {section === 'schedules' && } {section === 'logs' && } + {section === 'lab' && } diff --git a/admin_ui/src/api.js b/admin_ui/src/api.js index 6e8ac20..ef640bc 100644 --- a/admin_ui/src/api.js +++ b/admin_ui/src/api.js @@ -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`, { diff --git a/admin_ui/src/components/RequestLab.jsx b/admin_ui/src/components/RequestLab.jsx new file mode 100644 index 0000000..d6ae7cd --- /dev/null +++ b/admin_ui/src/components/RequestLab.jsx @@ -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 ( +
+
+
+
+

API-testomgeving

+

Voer een van de goedgekeurde verzoeken uit om het live antwoord te bekijken.

+
+
+ +
+ + +
+ +
+