Architecture
Frontend application
The DataFlow AI Platform user interface is a single-page application built on React 19, Vite 7 and TypeScript 5.9. It presents the entire ETL/ELT product — pipeline design, monitoring, governance, migration and administration — through one persona-adaptive shell that talks exclusively to the API gateway under /api/v1.
Tech stack
The SPA lives in frontend/ and is built and served independently from the JVM platform services. The dependency set is deliberately modern and centred on a small number of well-supported libraries.
| Concern | Library | Version |
|---|---|---|
| UI runtime | react / react-dom | 19.2 |
| Build / dev server | vite + @vitejs/plugin-react | 7.3 |
| Routing | react-router-dom | 7.13 (createBrowserRouter) |
| Client state | zustand | 5.0 (with persist middleware) |
| Server state | @tanstack/react-query | 5.90 |
| Tables | @tanstack/react-table 8.21, @tanstack/react-virtual 3.13 | |
| Flow / graph editor | @xyflow/react | 12.10 (React Flow) |
| Charts | recharts | 3.7 |
| Code editor | @monaco-editor/react | 4.7 |
| Forms | react-hook-form 7.71 + @hookform/resolvers + zod 4.3 | |
| Auth | keycloak-js | 26.2 |
| HTTP | axios | 1.13 |
| i18n | i18next 25.8 + react-i18next 16.5 + browser language detector + http-backend | |
| Icons | lucide-react | 0.576 |
| Command palette | cmdk | 1.1 |
| Dates | date-fns | 4.1 |
| Error handling | react-error-boundary | 5.0 |
| Styling | tailwindcss 4.2 via @tailwindcss/vite | |
| Testing | vitest 3.2, @testing-library/react, playwright 1.58, msw 2.12 |
The application is served in production by nginx (host port 3006) and reaches the backend exclusively through the API gateway.
Single ingress
The frontend never calls a microservice directly. Every request goes through the API gateway at /api/v1, which validates the Keycloak JWT, injects identity headers and applies rate limiting before proxying to a downstream service.
App bootstrap
The root component App.tsx is intentionally minimal. It wraps <RouterProvider router={router}> in a top-level react-error-boundary ErrorBoundary whose fallback is ErrorFallback and whose reset action navigates the browser to /. On mount it reads theme from useThemeStore and synchronises the dark class on document.documentElement.
App.tsx
└─ ErrorBoundary (fallback: ErrorFallback, reset → window.location.href = '/')
└─ RouterProvider (router from router.tsx)
└─ AppShell (route '/')
└─ ProtectedRoute → <Outlet/> (every in-shell page)
Each page is lazy()-loaded and wrapped in a SuspenseWrap helper — an ErrorBoundary plus a Suspense with a spinner fallback — so a failure or slow chunk in one page never breaks the rest of the shell.
The app shell
components/shell/AppShell.tsx renders the persistent layout for every in-app route. It composes navigation chrome, the main content outlet, the AI Copilot, and several global overlays.
┌──────────────────────────────────────────────────────────────────────┐
│ TopBar [breadcrumbs] [search ⌘K] [lang] [notifs] [user] │
├────────────┬───────────────────────────────────────────────────────────┤
│ │ │
│ Sidebar │ <main id="main-content"> │
│ │ │
│ ▸ Home │ <Outlet/> — the active page │
│ ▸ Design │ │
│ Studio │ │
│ ▸ Monitor │ ┌────────┐ │
│ ▸ Govern. │ │ AI │ │
│ ▸ Migrate │ │Copilot │ │
│ ▸ Data │ │Sidebar │ │
│ ▸ Admin │ │(toggle)│ │
│ │ └────────┘ │
│ [collapse] │ (●) Copilot │
├────────────┴───────────────────────────────────────────────────────────┤
│ MobileBottomNav (mobile only) │
└──────────────────────────────────────────────────────────────────────┘
Shell composition
- Sidebar (
Sidebar.tsx) — desktop and tablet navigation; collapsible between 60px and 240px, with hover-expand on tablet. - MobileMenu and MobileBottomNav — mobile-only navigation overlays.
- TopBar — header with breadcrumbs, global search trigger, language switcher, notifications and the user footer.
<main id="main-content">— responsive padding, renders the routed<Outlet />.- AICopilotTrigger (a floating action button) and AICopilotSidebar, shown conditionally when
copilotOpenis set. - GlobalSearch modal — opened with
Ctrl/Cmd+Kvia a keydown handler in the shell; backed by a real ranked search endpoint. - OnboardingTour — displayed until
tourCompletedis true inonboardingStore. - HelpDrawer and CatalogAskPanel — driven by local component state (
helpOpen,askOpen). - A skip-navigation link to
#main-contentfor keyboard and screen-reader users.
Responsive behaviour is computed by useIsMobile, useIsTablet and useSidebarState from hooks/useResponsive; an effectiveMargin value offsets the main content to match the current sidebar width.
ProtectedRoute
components/shell/ProtectedRoute.tsx wraps every in-shell route element and enforces authentication and authorization:
- While
isLoading, it renders a spinner labelled "Checking access...". - If the user is not authenticated, it renders
<Navigate to="/login">, carrying the originatingfromlocation in router state. - It computes an
effectivePersona— in dev mode frompersonaStore, otherwise fromuser.personaderived from Keycloak roles. - It calls
canAccess(persona, pathname)fromdata/permissions; if access is denied it renders<AccessDenied />.
Every decision is logged through createLogger('auth.protected-route').
Route map
Routing is defined in frontend/src/router.tsx using createBrowserRouter. Routes split into two groups: a small set rendered outside the shell (no chrome), and the bulk rendered inside AppShell and wrapped in ProtectedRoute.
Routes outside the shell
| Path | Page | Purpose |
|---|---|---|
/login | Login | Keycloak SSO login entry |
/callback | Callback | OIDC redirect callback handler |
/unauthorized | Unauthorized | Auth failure / no-session page |
/onboarding | Onboarding | First-login onboarding flow |
/report/:pipelineId | PipelineReport | Standalone pipeline report, clean print-to-PDF layout |
Core in-shell routes
| Path | Page | Purpose |
|---|---|---|
/ (index) | HomeDashboard | Home / overview dashboard |
/design-studio | DesignStudio | Visual pipeline designer (React Flow) |
/design-studio/:pipelineId | DesignStudio | Edit an existing pipeline |
/data-browser | DataBrowser | Data catalog / schema browser |
/data-browser/:tableId | DataBrowser | Table detail |
/data-browser/:tableId/columns/:columnName | DataBrowser | Column detail |
/my-pipelines | MyPipelines | The user's pipeline list |
/pipelines/new | CreatePipelineWizard | Create-pipeline wizard |
/pipelines/:id | PipelineDetail | Pipeline detail view |
/pipelines/:id/edit | CreatePipelineWizard | Edit a pipeline via wizard |
/connections/new | ConnectionWizard | Connection setup wizard |
/connections/cdc | CdcConnectionsPage | CDC connections operator page |
/data-marketplace | DataMarketplacePage | Data products marketplace |
/data-marketplace/products/:id | DataProductDetail | Data product detail |
/marketplace | ConnectorMarketplace | Connector marketplace |
/templates | PipelineTemplates | Pipeline templates gallery |
/telecom/subscriber-360 | Subscriber360TemplatePage | Telecom Subscriber 360 template |
Monitor — /monitor (MonitorLayout, nested)
| Path | Page | Purpose |
|---|---|---|
/monitor | redirects to runs | |
/monitor/runs | PipelineRunsDashboard | Pipeline run list |
/monitor/runs/:runId | PipelineRunDetail | Single run detail |
/monitor/runs/:runId/logs | LogViewerPage | Run log viewer |
/monitor/performance | PerformanceAnalytics | Performance analytics |
/monitor/alerts | AlertManagement | Alert management |
/monitor/logs | LogViewerPage | Global log viewer |
/monitor/self-healing | SelfHealingDashboard | Self-healing dashboard |
/monitor/quarantine | DataQuarantine | Quarantined data |
/monitor/freshness | DataFreshness | Data freshness monitor |
/monitor/sla | SlaBurnRate | SLA burn-rate |
/monitor/costs | PipelineCosts | Pipeline cost dashboard |
Migration — /migration (MigrationLayout, nested)
| Path | Page | Purpose |
|---|---|---|
/migration | redirects to import | |
/migration/import | ImportWizardPage | Import wizard |
/migration/conversion | ConversionDashboardPage | Conversion dashboard |
/migration/validation | ValidationSuitePage | Validation suite |
/migration/progress | ProgressTrackerPage | Migration progress tracker |
Governance — /governance (GovernanceHub, nested)
| Path | Page | Purpose |
|---|---|---|
/governance | redirects to lineage | |
/governance/lineage | LineageExplorer | Data lineage graph |
/governance/quality | QualityMonitoring | Data-quality monitoring |
/governance/reviews | ReviewQueue | Governance review queue |
/governance/reviews/:reviewId | GovernanceReviewDetailPage | Review detail |
/governance/glossary | BusinessGlossary | Business glossary |
/governance/audit | AuditTrail | Audit trail |
/governance/schema-evolution | SchemaEvolution | Schema-evolution tracking |
/governance/contracts | DataContracts | Data contracts |
/governance/compliance | ComplianceDashboard | Polish regulatory compliance |
/governance/dsar | DsarRequestsPage | GDPR DSAR requests list |
/governance/dsar/:id | DsarRequestDetailPage | DSAR request detail |
/governance/masking | MaskingPolicyEditor | PII masking-policy editor |
/governance/access-revocation | AccessRevocationQueue | Access-revocation queue |
/governance/data-versions | DataVersionsPage | Data version history |
Admin — /admin (AdminConsole, nested)
| Path | Page | Purpose |
|---|---|---|
/admin | redirects to users | |
/admin/users | UsersWorkspaces | Users and workspaces |
/admin/security | Security | Security settings (SSO, sessions) |
/admin/infrastructure | Infrastructure | Service-health and infrastructure |
/admin/costs | CostManagement | Cost management |
/admin/environments | Environments | Environment promotion |
/admin/reports | ScheduledReports | Scheduled reports |
/admin/ai-provider | AiProviderSettings | AI/LLM provider configuration |
/admin/bootstrap | BootstrapWizardPage | Platform bootstrap wizard |
/admin/incidents/runbooks | RunbookListPage | Incident runbook list |
/admin/incidents/runbooks/:id | RunbookExecutionPage | Runbook execution |
/admin/audit-log | AuditLogPage | System audit log |
/admin/access-reviews | AccessReviewCycles | Access-review cycles |
/admin/access-reviews/cycles/:cycleId | CycleDetail | Access-review cycle detail |
/admin/tag-policies | TagPoliciesPage | Tag policies |
/admin/regulatory-frameworks | RegulatoryFrameworks | Regulatory framework templates |
/admin/workflows | WorkflowDesigner | Workflow automation designer |
The catch-all route /* renders <Navigate to="/" replace />.
Unrouted legacy files
Several files in pages/ are not wired into the router — legacy or duplicate variants such as LoginPage.tsx, MigrationCenter.tsx, MonitorCenter.tsx, ConnectorMarketplacePage.tsx and PolishCompliance.tsx. The router uses the de-suffixed variants. The route file carries a lock-in inventory comment for the wave6-router-contract.test.ts test.
Component architecture
Components are organised by feature area under frontend/src/components/.
| Area | Folder | Key components |
|---|---|---|
| Shell / navigation | shell/ | AppShell, Sidebar, TopBar, ProtectedRoute, MobileMenu, MobileBottomNav, BreadcrumbNav, CommandPalette, SearchModal, LanguageSwitcher, NotificationCenter, OnboardingTour |
| Design Studio | design-studio/ | PipelineToolbar, PipelineSelector, NodeDetailPanel, SaveIndicator, VersionHistory, GitDiffViewer, ExportReportModal, StreamingNodeTypes, nodes/PipelineNode, editors/SchemaExplorer |
| Monitor | monitor/ | MonitorLayout, GanttChart, PipelineDetailPanel, PipelineRunsTable |
| Governance | governance/ | GovernanceReviewModal, quality/QualityOverview, lineage/ (LineageGraph, LineageNode, LineageMiniMap, ImpactAnalysisPanel) |
| Admin | admin/ | IncidentPanel, users/ (UserTable, RoleMatrix, ADGroupMapping), infra/ (ServiceHealthGrid), costs/, envs/, security/ (SSOConfig, SessionManager) |
| Migration | migration/ | MigrationLayout (wizard pages live in pages/migration/) |
| AI Copilot | ai-copilot/ | AICopilotSidebar, AICopilotTrigger, ChatMessage, ChatInput, QuickActions, InsightCard, DiffViewer, ErrorDiagnosis |
| Data Browser | data-browser/ | CatalogSearch, ConnectionTree, SchemaViewer, TableCard, ColumnDetail, DataProfile, SampleDataPreview |
| Pipelines | pipelines/ | CreatePipelineWizard, PipelineCard, PipelineListItem, PipelinePagination, DeleteConfirmModal |
| Connections | connections/ | ConnectionWizard, ConnectorTypeCard, DatabaseIcons |
| Layout primitives | layout/ | PageContainer, PageHeader, SplitPane |
| UI kit | ui/ | ~40 primitives — DataTable, DataGrid, Tabs, Badge, Input, Select, Timeline, CodeBlock, Toast, charts, PermissionGate |
The ui/ kit holds about forty presentation primitives — form controls, tables, navigation aids, charts (LineChart, BarChart, GaugeChart), layout helpers (Stack, ResponsiveGrid, Divider) and accessibility helpers such as SkipNav and PermissionGate.
State management
Zustand stores
Client state lives in twelve Zustand stores under frontend/src/stores/. All are created with create(); four wrap the persist middleware to back their state with localStorage under dataflow-* keys.
| Store | Persisted key | Purpose |
|---|---|---|
themeStore | dataflow-theme | theme: 'dark' | 'light' (default dark); toggles the <html>.dark class |
personaStore | dataflow-persona | activePersona (default engineer), isKeycloakAuthenticated; four personas |
shellStore | dataflow-shell (partial) | sidebar expanded/hovered/collapsed, mobile menu, panel-open flags |
onboardingStore | persisted | onboarding tour completion state |
copilotStore | — | AI copilot chat and insight state |
pipelineStore | — | pipeline designer state |
catalogStore | — | data catalog browsing state |
governanceStore | — | governance feature state |
migrationStore | — | migration workflow state |
monitorStore | — | monitor view state |
connectionStore | — | connection wizard state |
adminStore | — | admin console state |
Server state
Server data is managed with TanStack React Query 5 through a generic useApiQuery hook and per-feature data hooks such as usePipelineData, useCatalogData, useMonitorData, useGovernanceData and useAdminData. React Query handles caching, deduplication and revalidation; Axios performs the underlying transport.
API client layer
Base client
The base client is an Axios instance in api/client.ts. Its baseURL is VITE_API_URL ?? '/api/v1', its timeout is VITE_API_TIMEOUT ?? 30s, and withCredentials is deliberately false — setting it true stalled chunked gateway responses.
The client implements several cross-cutting behaviours:
- Token accessor injection.
registerTokenAccessor(fn)is called once byAuthProvider, avoiding a circular import of the Keycloak singleton. - Auth-readiness gate.
signalAuthReady()resolves a promise onceKeycloak.init()completes. The request interceptorawaits it — racing against the requestAbortSignaland a 5-second safety timeout — so the first/api/v1/*calls always carry a JWT and do not 401. - Request interceptor. Attaches
Authorization: Bearer <token>; on POST/PUT/PATCH/DELETE it addsX-CSRF-Token, read from a<meta name="csrf-token">tag or theXSRF-TOKENcookie; emits dev logging. - Response interceptor. On a 401 it refreshes the token once — guarded by an
x-retry-after-refreshheader — and retries the request. It recursively convertssnake_casekeys tocamelCase(the backend's Jackson ships SNAKE_CASE) and normalizes every error into anApiErrorcarryingstatus,codeanderrors, with helper gettersisUnauthorized,isForbidden,isNotFoundandisValidation.
Convenience wrappers apiGet, apiPost, apiPut, apiPatch and apiDelete sit on top.
Domain API modules
Roughly fifty per-feature typed wrappers live under api/ — for example pipelines, pipelineTemplates, connections, cdc, cdr, catalog, copilot, governance, lineage, quality, monitoring, metrics, migration, dataProducts, masking, gdpr, auditLog, admin and search. api/index.ts is the barrel re-export.
Real-time transport
The services/ folder holds sse.ts / SSEManager.ts for Server-Sent Events (live alerts, metrics, copilot streaming), websocket.ts / WebSocketManager.ts for WebSocket transport, and pipelineValidator.ts for client-side pipeline validation. Real-time data is consumed through hooks such as useAlertStream, useMetricsStream, useCopilotStream, usePipelineStream and useMonitorWebSocket.
Authentication
Keycloak configuration
auth/keycloak.ts holds a singleton keycloakInstance configured from VITE_KEYCLOAK_URL (default http://localhost:8180), VITE_KEYCLOAK_REALM (dataflow) and VITE_KEYCLOAK_CLIENT_ID (dataflow-app). The instance is exposed on window.__keycloak for Playwright diagnostics. Init options are onLoad: 'check-sso', pkceMethod: 'S256', a silentCheckSsoRedirectUri pointing at /silent-check-sso.html, and checkLoginIframe: false.
A ROLE_PERSONA_MAP accepts both Keycloak-native role names (org_admin, workspace_admin, developer, operator, data_steward, analyst) and Spring ROLE_* authorities, mapping them — priority-ordered, defaulting to analyst — onto four personas: admin, steward, engineer and analyst. Key constants are TOKEN_REFRESH_BUFFER_SECONDS = 30, SESSION_TIMEOUT_WARNING_SECONDS = 60 and MIN_TOKEN_VALIDITY_SECONDS = 60.
Auth flow
auth/AuthProvider.tsx provides a React context, consumed via useAuth(), exposing isAuthenticated, isLoading, isDevMode, user, token, personas, login(), logout(), hasRole() and hasPermission().
mount AuthProvider
│
├─ register token accessor with api/client
│
├─ keycloakInstance.init() ──── Promise.race ──── 15s timeout
│ │ │
│ success failure
│ │ │
│ build KeycloakUserProfile allowDevModeFallback?
│ from tokenParsed: ┌──────┴───────┐
│ realm_access.roles, names, yes no
│ email, workspace_id │ │
│ │ dev mode unauthenticated
│ derive persona + permissions (personaStore +
│ │ buildDevProfile)
│ schedule silent refresh (30s pre-expiry)
│ schedule session warning (60s pre-expiry)
│ │
└────────┴──► signalAuthReady() → unblocks the API interceptor
- On mount,
AuthProviderregisters the token accessor withapi/clientand runskeycloakInstance.init()under a 15-secondPromise.racetimeout. - On success it builds a
KeycloakUserProfilefromtokenParsed—realm_access.roles,given_name/family_name,email,workspace_id— and derives a persona pluspersonaPermissions. - It schedules a silent token refresh via
updateToken30 seconds before expiry, and a session-timeout warning 60 seconds before expiry that opensSessionTimeoutModal. - On init failure, if
allowDevModeFallbackis true it enters dev mode usingpersonaStoreandbuildDevProfile; otherwise it stays unauthenticated. - It always calls
signalAuthReady()after init, regardless of outcome, which unblocks the API request interceptor. login()redirects to Keycloak (default redirect/);logout()clears timers and state and redirects to/login.
Dev authentication
auth/devAuth.ts defines four hard-coded dev personas — Jan Kowalski (engineer), Anna Nowak (analyst), Piotr Wiśniewski (admin) and Maria Kamińska (steward), all on @polkomtel.pl. Playwright end-to-end tests use a VITE_AUTH_MODE=dev bypass. SessionTimeoutModal is only shown in real, non-dev mode.
Internationalization
i18n/index.ts wires i18next with react-i18next and i18next-browser-languagedetector.
- Languages:
enandpl— English and Polish.supportedLanguages = ['en','pl']withfallbackLng: 'en'. - Eight namespaces:
common,admin,governance,pipeline,monitor,migration,catalogandcopilot. JSON resources are bundled per language underi18n/locales/{en,pl}/. - Detection order:
localStorage(keydataflow-language) thennavigator; the chosen language is cached back tolocalStorage. react.useSuspenseisfalse;returnNullandreturnEmptyStringare bothfalse.- Translations are accessed through the
useThook, and the shell exposes aLanguageSwitchercomponent.
The sidebar navigation model in data/navigation.ts keys its labels through navI18nKeys under the nav.* prefix, so the entire navigation tree is translatable.
Theming and design tokens
Theming is driven by themeStore with a default of dark. The store toggles the dark class on <html>, and Tailwind CSS 4 resolves semantic design tokens against that class. Components reference token classes rather than raw colours — bg-surface-0/1/2, text-heading/body/secondary/muted, border-primary — with amber-500 as the brand accent.
Forms use react-hook-form with zod resolvers via the useFormWithSchema hook. Errors are caught at three levels: a global react-error-boundary in App.tsx, a per-route ErrorBoundary inside SuspenseWrap, and a PanelErrorFallback for panel-scoped failures. Every page is lazy()-loaded with a Suspense spinner fallback for code splitting.
See also
The design language — tokens, the dark amber/zinc theme, component conventions and the persona-adaptive shell — is documented in full on the Design studio system & UI conventions page.