diff --git a/CURRENT_STATE.md b/CURRENT_STATE.md index 1e0c4d1..5f4b3c6 100644 --- a/CURRENT_STATE.md +++ b/CURRENT_STATE.md @@ -118,7 +118,7 @@ report exported. CLI and web inspector. - ✅ 17 integration tests (execa-based) - ✅ Converted JSON fixtures to TS modules (fixes Node.js ESM JSON import issue) -### Next.js App Scaffold (in progress — this PR) +### Next.js App Scaffold (PR #42) - ✅ `apps/web/` — single Next.js app (App Router) - ✅ Tailwind CSS initialized @@ -126,6 +126,17 @@ report exported. CLI and web inspector. - ✅ Workspace dependencies on `@ocpp-debugkit/core`, `scenarios`, `reporter` - ✅ `"private": true` (never publishable to npm) +### Landing Page + Inspector (in progress — this PR) + +- ✅ Landing page: hero, features, what-it's-not, architecture, quick start, footer +- ✅ Inspector: trace paste textarea + file upload + sample scenario selector +- ✅ Inspector: session timeline (click events to inspect) +- ✅ Inspector: message inspector panel (raw + normalized fields) +- ✅ Inspector: failure summary (severity, description, suggested steps) +- ✅ Inspector: Markdown report export (download) +- ✅ Inspector: loading/empty/error states +- ✅ Browser-local processing only (all parsing client-side) + ## What's Next 1. **Issue #20** → complete (PR #33): data model + parser + normalizer @@ -134,9 +145,11 @@ report exported. CLI and web inspector. 4. **Issue #23** → complete (PR #39): scenarios package (format + 5 initial scenarios) 5. **Issue #24** → complete (PR #40): reporter package (Markdown report generator) 6. **Issue #25** → complete (PR #41): CLI package (scaffold + inspect + report + scenario commands) -7. **Issue #26** (this PR) → complete: Next.js app scaffold (Next.js + Tailwind, routes for /, /inspector, /docs) -8. **Issue #27**: Landing page (hero, features, architecture, quick start, footer) -9. **Issue #28**: Inspector (trace input + timeline + message inspector) +7. **Issue #26** → complete (PR #42): Next.js app scaffold (Next.js + Tailwind, routes for /, /inspector, /docs) +8. **Issue #27** (this PR) → complete: Landing page (hero, features, architecture, quick start, footer) +9. **Issue #28** (this PR) → complete: Inspector (trace input + timeline + message inspector + failures + report export) +10. **Issue #29**: Inspector polish (responsive, keyboard nav, loading states) +11. **Issue #30**: Playwright smoke tests ## Known Blockers / Decisions Pending @@ -152,4 +165,4 @@ report exported. CLI and web inspector. | `@ocpp-debugkit/cli` | in progress (inspect + report + scenario) | 0.0.0 | | `@ocpp-debugkit/replay` | not started | — | | `@ocpp-debugkit/react` | not started | — | -| `apps/web` | in progress (scaffold + routes) | — | +| `apps/web` | in progress (landing + inspector) | — | diff --git a/apps/web/src/app/inspector/page.tsx b/apps/web/src/app/inspector/page.tsx index 235a638..a9708d6 100644 --- a/apps/web/src/app/inspector/page.tsx +++ b/apps/web/src/app/inspector/page.tsx @@ -1,13 +1,419 @@ +'use client'; + +import { useState, useCallback, useMemo } from 'react'; +import { + parseTrace, + buildSessionTimeline, + detectFailures, + summarizeSessions, + type Event, + type Failure, + type Session, + type SessionSummary, + type ParseWarning, + type Scenario, + ParseError, + MAX_INPUT_SIZE_BYTES, +} from '@ocpp-debugkit/core'; +import { generateMarkdownReport } from '@ocpp-debugkit/reporter'; +import { scenarios } from '@ocpp-debugkit/scenarios'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface AnalysisState { + events: Event[]; + sessions: Session[]; + failures: Failure[]; + summaries: SessionSummary[]; + warnings: ParseWarning[]; + selectedEventId: string | null; + error: string | null; +} + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + export default function InspectorPage() { + const [input, setInput] = useState(''); + const [analysis, setAnalysis] = useState(null); + + const selectedEvent = useMemo(() => { + if (!analysis || !analysis.selectedEventId) return null; + return analysis.events.find((e) => e.id === analysis.selectedEventId) ?? null; + }, [analysis]); + + const handleAnalyze = useCallback(() => { + if (!input.trim()) { + setAnalysis({ + events: [], + sessions: [], + failures: [], + summaries: [], + warnings: [], + selectedEventId: null, + error: 'Input is empty.', + }); + return; + } + + try { + const result = parseTrace(input); + const sessions = buildSessionTimeline(result.events); + const failures = detectFailures(result.events, sessions); + const summaries = summarizeSessions(sessions, failures); + + setAnalysis({ + events: result.events, + sessions, + failures, + summaries, + warnings: result.warnings, + selectedEventId: null, + error: null, + }); + } catch (e) { + const message = + e instanceof ParseError ? e.message : e instanceof Error ? e.message : 'Unknown error'; + setAnalysis({ + events: [], + sessions: [], + failures: [], + summaries: [], + warnings: [], + selectedEventId: null, + error: message, + }); + } + }, [input]); + + const handleFileUpload = useCallback(async (file: File) => { + if (file.size > MAX_INPUT_SIZE_BYTES) { + setAnalysis({ + events: [], + sessions: [], + failures: [], + summaries: [], + warnings: [], + selectedEventId: null, + error: `File size exceeds ${MAX_INPUT_SIZE_BYTES} bytes.`, + }); + return; + } + const text = await file.text(); + setInput(text); + }, []); + + const handleScenarioSelect = useCallback((scenario: Scenario) => { + setInput(JSON.stringify(scenario.trace, null, 2)); + }, []); + + const handleExportReport = useCallback(() => { + if (!analysis) return; + const report = generateMarkdownReport({ + events: analysis.events, + sessions: analysis.sessions, + failures: analysis.failures, + summaries: analysis.summaries, + warnings: analysis.warnings, + }); + const blob = new Blob([report], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'ocpp-debugkit-report.md'; + a.click(); + URL.revokeObjectURL(url); + }, [analysis]); + return ( -
-
-

OCPP Inspector

-

- Inspect OCPP charging traces, view session timelines, and detect failures. -

-

The inspector UI is under construction.

+
+ {/* Header */} +
+
+

OCPP Inspector

+ {analysis && analysis.events.length > 0 && ( + + )} +
+
+ +
+ {/* Input section */} +
+ {/* Trace input */} +
+ +