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.

ConcernLibraryVersion
UI runtimereact / react-dom19.2
Build / dev servervite + @vitejs/plugin-react7.3
Routingreact-router-dom7.13 (createBrowserRouter)
Client statezustand5.0 (with persist middleware)
Server state@tanstack/react-query5.90
Tables@tanstack/react-table 8.21, @tanstack/react-virtual 3.13
Flow / graph editor@xyflow/react12.10 (React Flow)
Chartsrecharts3.7
Code editor@monaco-editor/react4.7
Formsreact-hook-form 7.71 + @hookform/resolvers + zod 4.3
Authkeycloak-js26.2
HTTPaxios1.13
i18ni18next 25.8 + react-i18next 16.5 + browser language detector + http-backend
Iconslucide-react0.576
Command palettecmdk1.1
Datesdate-fns4.1
Error handlingreact-error-boundary5.0
Stylingtailwindcss 4.2 via @tailwindcss/vite
Testingvitest 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 copilotOpen is set.
  • GlobalSearch modal — opened with Ctrl/Cmd+K via a keydown handler in the shell; backed by a real ranked search endpoint.
  • OnboardingTour — displayed until tourCompleted is true in onboardingStore.
  • HelpDrawer and CatalogAskPanel — driven by local component state (helpOpen, askOpen).
  • A skip-navigation link to #main-content for 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:

  1. While isLoading, it renders a spinner labelled "Checking access...".
  2. If the user is not authenticated, it renders <Navigate to="/login">, carrying the originating from location in router state.
  3. It computes an effectivePersona — in dev mode from personaStore, otherwise from user.persona derived from Keycloak roles.
  4. It calls canAccess(persona, pathname) from data/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

PathPagePurpose
/loginLoginKeycloak SSO login entry
/callbackCallbackOIDC redirect callback handler
/unauthorizedUnauthorizedAuth failure / no-session page
/onboardingOnboardingFirst-login onboarding flow
/report/:pipelineIdPipelineReportStandalone pipeline report, clean print-to-PDF layout

Core in-shell routes

PathPagePurpose
/ (index)HomeDashboardHome / overview dashboard
/design-studioDesignStudioVisual pipeline designer (React Flow)
/design-studio/:pipelineIdDesignStudioEdit an existing pipeline
/data-browserDataBrowserData catalog / schema browser
/data-browser/:tableIdDataBrowserTable detail
/data-browser/:tableId/columns/:columnNameDataBrowserColumn detail
/my-pipelinesMyPipelinesThe user's pipeline list
/pipelines/newCreatePipelineWizardCreate-pipeline wizard
/pipelines/:idPipelineDetailPipeline detail view
/pipelines/:id/editCreatePipelineWizardEdit a pipeline via wizard
/connections/newConnectionWizardConnection setup wizard
/connections/cdcCdcConnectionsPageCDC connections operator page
/data-marketplaceDataMarketplacePageData products marketplace
/data-marketplace/products/:idDataProductDetailData product detail
/marketplaceConnectorMarketplaceConnector marketplace
/templatesPipelineTemplatesPipeline templates gallery
/telecom/subscriber-360Subscriber360TemplatePageTelecom Subscriber 360 template

Monitor — /monitor (MonitorLayout, nested)

PathPagePurpose
/monitorredirects to runs
/monitor/runsPipelineRunsDashboardPipeline run list
/monitor/runs/:runIdPipelineRunDetailSingle run detail
/monitor/runs/:runId/logsLogViewerPageRun log viewer
/monitor/performancePerformanceAnalyticsPerformance analytics
/monitor/alertsAlertManagementAlert management
/monitor/logsLogViewerPageGlobal log viewer
/monitor/self-healingSelfHealingDashboardSelf-healing dashboard
/monitor/quarantineDataQuarantineQuarantined data
/monitor/freshnessDataFreshnessData freshness monitor
/monitor/slaSlaBurnRateSLA burn-rate
/monitor/costsPipelineCostsPipeline cost dashboard

Migration — /migration (MigrationLayout, nested)

PathPagePurpose
/migrationredirects to import
/migration/importImportWizardPageImport wizard
/migration/conversionConversionDashboardPageConversion dashboard
/migration/validationValidationSuitePageValidation suite
/migration/progressProgressTrackerPageMigration progress tracker

Governance — /governance (GovernanceHub, nested)

PathPagePurpose
/governanceredirects to lineage
/governance/lineageLineageExplorerData lineage graph
/governance/qualityQualityMonitoringData-quality monitoring
/governance/reviewsReviewQueueGovernance review queue
/governance/reviews/:reviewIdGovernanceReviewDetailPageReview detail
/governance/glossaryBusinessGlossaryBusiness glossary
/governance/auditAuditTrailAudit trail
/governance/schema-evolutionSchemaEvolutionSchema-evolution tracking
/governance/contractsDataContractsData contracts
/governance/complianceComplianceDashboardPolish regulatory compliance
/governance/dsarDsarRequestsPageGDPR DSAR requests list
/governance/dsar/:idDsarRequestDetailPageDSAR request detail
/governance/maskingMaskingPolicyEditorPII masking-policy editor
/governance/access-revocationAccessRevocationQueueAccess-revocation queue
/governance/data-versionsDataVersionsPageData version history

Admin — /admin (AdminConsole, nested)

PathPagePurpose
/adminredirects to users
/admin/usersUsersWorkspacesUsers and workspaces
/admin/securitySecuritySecurity settings (SSO, sessions)
/admin/infrastructureInfrastructureService-health and infrastructure
/admin/costsCostManagementCost management
/admin/environmentsEnvironmentsEnvironment promotion
/admin/reportsScheduledReportsScheduled reports
/admin/ai-providerAiProviderSettingsAI/LLM provider configuration
/admin/bootstrapBootstrapWizardPagePlatform bootstrap wizard
/admin/incidents/runbooksRunbookListPageIncident runbook list
/admin/incidents/runbooks/:idRunbookExecutionPageRunbook execution
/admin/audit-logAuditLogPageSystem audit log
/admin/access-reviewsAccessReviewCyclesAccess-review cycles
/admin/access-reviews/cycles/:cycleIdCycleDetailAccess-review cycle detail
/admin/tag-policiesTagPoliciesPageTag policies
/admin/regulatory-frameworksRegulatoryFrameworksRegulatory framework templates
/admin/workflowsWorkflowDesignerWorkflow 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/.

AreaFolderKey components
Shell / navigationshell/AppShell, Sidebar, TopBar, ProtectedRoute, MobileMenu, MobileBottomNav, BreadcrumbNav, CommandPalette, SearchModal, LanguageSwitcher, NotificationCenter, OnboardingTour
Design Studiodesign-studio/PipelineToolbar, PipelineSelector, NodeDetailPanel, SaveIndicator, VersionHistory, GitDiffViewer, ExportReportModal, StreamingNodeTypes, nodes/PipelineNode, editors/SchemaExplorer
Monitormonitor/MonitorLayout, GanttChart, PipelineDetailPanel, PipelineRunsTable
Governancegovernance/GovernanceReviewModal, quality/QualityOverview, lineage/ (LineageGraph, LineageNode, LineageMiniMap, ImpactAnalysisPanel)
Adminadmin/IncidentPanel, users/ (UserTable, RoleMatrix, ADGroupMapping), infra/ (ServiceHealthGrid), costs/, envs/, security/ (SSOConfig, SessionManager)
Migrationmigration/MigrationLayout (wizard pages live in pages/migration/)
AI Copilotai-copilot/AICopilotSidebar, AICopilotTrigger, ChatMessage, ChatInput, QuickActions, InsightCard, DiffViewer, ErrorDiagnosis
Data Browserdata-browser/CatalogSearch, ConnectionTree, SchemaViewer, TableCard, ColumnDetail, DataProfile, SampleDataPreview
Pipelinespipelines/CreatePipelineWizard, PipelineCard, PipelineListItem, PipelinePagination, DeleteConfirmModal
Connectionsconnections/ConnectionWizard, ConnectorTypeCard, DatabaseIcons
Layout primitiveslayout/PageContainer, PageHeader, SplitPane
UI kitui/~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.

StorePersisted keyPurpose
themeStoredataflow-themetheme: 'dark' | 'light' (default dark); toggles the <html>.dark class
personaStoredataflow-personaactivePersona (default engineer), isKeycloakAuthenticated; four personas
shellStoredataflow-shell (partial)sidebar expanded/hovered/collapsed, mobile menu, panel-open flags
onboardingStorepersistedonboarding tour completion state
copilotStoreAI copilot chat and insight state
pipelineStorepipeline designer state
catalogStoredata catalog browsing state
governanceStoregovernance feature state
migrationStoremigration workflow state
monitorStoremonitor view state
connectionStoreconnection wizard state
adminStoreadmin 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 by AuthProvider, avoiding a circular import of the Keycloak singleton.
  • Auth-readiness gate. signalAuthReady() resolves a promise once Keycloak.init() completes. The request interceptor awaits it — racing against the request AbortSignal and 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 adds X-CSRF-Token, read from a <meta name="csrf-token"> tag or the XSRF-TOKEN cookie; emits dev logging.
  • Response interceptor. On a 401 it refreshes the token once — guarded by an x-retry-after-refresh header — and retries the request. It recursively converts snake_case keys to camelCase (the backend's Jackson ships SNAKE_CASE) and normalizes every error into an ApiError carrying status, code and errors, with helper getters isUnauthorized, isForbidden, isNotFound and isValidation.

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
  1. On mount, AuthProvider registers the token accessor with api/client and runs keycloakInstance.init() under a 15-second Promise.race timeout.
  2. On success it builds a KeycloakUserProfile from tokenParsedrealm_access.roles, given_name/family_name, email, workspace_id — and derives a persona plus personaPermissions.
  3. It schedules a silent token refresh via updateToken 30 seconds before expiry, and a session-timeout warning 60 seconds before expiry that opens SessionTimeoutModal.
  4. On init failure, if allowDevModeFallback is true it enters dev mode using personaStore and buildDevProfile; otherwise it stays unauthenticated.
  5. It always calls signalAuthReady() after init, regardless of outcome, which unblocks the API request interceptor.
  6. 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: en and pl — English and Polish. supportedLanguages = ['en','pl'] with fallbackLng: 'en'.
  • Eight namespaces: common, admin, governance, pipeline, monitor, migration, catalog and copilot. JSON resources are bundled per language under i18n/locales/{en,pl}/.
  • Detection order: localStorage (key dataflow-language) then navigator; the chosen language is cached back to localStorage.
  • react.useSuspense is false; returnNull and returnEmptyString are both false.
  • Translations are accessed through the useT hook, and the shell exposes a LanguageSwitcher component.

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.

Previous
AI services