Frontend Development
Guide for developing the FacilFlow frontend.
Technology Stack
| Technology | Purpose |
|---|---|
| React 19 | UI framework with concurrent features |
| TypeScript | Type safety |
| shadcn/ui | Component library (built on Radix UI primitives) |
| Radix UI | Accessible, unstyled UI primitives |
| TailwindCSS | Utility-first CSS framework |
| @xyflow/react | Visual pipeline builder (React Flow) |
| Zustand | Lightweight client state management |
| TanStack Query | Server state, caching, mutations |
| Socket.IO Client | Real-time WebSocket communication |
| Webpack | Build and dev server |
Project Structure
src/renderer/
├── App.tsx # Root component, routing, providers
├── pages/ # 28 page components
│ ├── LoginPage.tsx # Firebase login
│ ├── SignupPage.tsx # User registration
│ ├── ProfilePage.tsx # User profile
│ ├── SettingsPage.tsx # App settings
│ ├── ConnectorsPage.tsx # Connector management
│ ├── ConnectorHubPage.tsx # Connector type browser
│ ├── AirbyteCatalogPage.tsx # PyAirbyte catalog
│ ├── TelegrafPluginsPage.tsx # Telegraf plugin browser
│ ├── APIRepositoryPage.tsx # API connector repository
│ ├── AgentsPage.tsx # Edge agent list
│ ├── AgentDetailPage.tsx # Agent details and metrics
│ ├── AgentConfigPage.tsx # Agent config editor
│ ├── DataSourcesPage.tsx # Data source browser
│ ├── ClustersPage.tsx # Compute clusters
│ ├── PipelinesPage.tsx # Pipeline list and builder
│ ├── MLModelsPage.tsx # ML model registry
│ ├── SchemaRegistryPage.tsx # Schema definitions
│ ├── DataExplorerPage.tsx # SQL query interface
│ ├── DashboardPage.tsx # Dashboard builder
│ ├── DataQualityPage.tsx # Quality rules and scores
│ ├── LineageExplorerPage.tsx # Lineage graph
│ ├── AlertsPage.tsx # Alert rules
│ ├── SchedulerPage.tsx # Pipeline scheduling
│ ├── StoragePage.tsx # Storage providers
│ ├── ObjectStoreManagerPage.tsx # Object/file browser
│ ├── AuditLogsPage.tsx # Activity audit trail
│ └── GovernancePage.tsx # Data governance
├── components/
│ ├── ui/ # shadcn/ui primitives
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── table.tsx
│ │ └── ...
│ ├── pipeline/ # Pipeline builder
│ │ ├── PipelineBuilder.tsx # React Flow canvas + drag-drop
│ │ ├── NodePalette.tsx # Draggable node types
│ │ └── nodes/ # Custom node components
│ │ ├── SourceNode.tsx # PI, OPC-UA, Modbus, MQTT
│ │ ├── ProcessorNode.tsx # Filter, transform, aggregate
│ │ └── SinkNode.tsx # InfluxDB, Kafka, S3
│ ├── agents/ # Agent cards, status indicators
│ ├── dashboard/ # Dashboard widgets
│ ├── data/ # Data explorer, quality, lineage
│ ├── ml/ # ML model cards, training UI
│ ├── storage/ # Provider selector, bucket browser
│ └── layout/ # Shell, sidebar, header
├── hooks/ # Custom hooks
│ ├── usePipelines.ts
│ ├── useAgents.ts
│ ├── useClusters.ts
│ ├── useStorage.ts
│ ├── useML.ts
│ └── ...
├── services/ # API client modules
│ ├── pipelineService.ts
│ ├── agentService.ts
│ ├── storageService.ts
│ ├── mlService.ts
│ ├── queryService.ts
│ └── ...
├── stores/ # Zustand stores
│ ├── authStore.ts
│ ├── pipelineStore.ts
│ └── ...
└── lib/ # Utilities, constants
├── utils.ts # cn() helper, formatters
└── constants.ts
Sidebar Navigation
The app uses a 5-section sidebar:
| Section | Icon | Pages |
|---|---|---|
| Connect | Plug | Connectors, Edge Agents, Data Sources |
| Infrastructure | Server | Clusters |
| Build | Hammer | Pipelines, ML Models, Schemas |
| Analyze | BarChart | Data Explorer, Dashboards, Data Quality, Lineage |
| Operate | Settings | Alerts, Scheduler, Storage, Audit Logs, Governance |
Theme and Styling
FacilFlow uses TailwindCSS with CSS custom properties for theming. Dark mode by default.
CSS Variables
:root {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 166 82% 32%; /* Teal #0D9488 */
--primary-foreground: 210 40% 98%;
--secondary: 217.2 32.6% 17.5%; /* Slate #1E293B */
--card: 222.2 84% 4.9%;
--border: 217.2 32.6% 17.5%;
--ring: 166 82% 32%;
}
Using the cn() Helper
import { cn } from '@/lib/utils';
<div className={cn(
"rounded-lg border bg-card p-4",
isActive && "border-primary"
)} />
Component Example
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
function PipelineCard({ pipeline }) {
return (
<Card>
<CardHeader>
<CardTitle>{pipeline.name}</CardTitle>
</CardHeader>
<CardContent>
<Button variant="default" onClick={handleStart}>
Start Pipeline
</Button>
</CardContent>
</Card>
);
}
Pipeline Builder
The pipeline builder uses @xyflow/react with custom node types:
const nodeTypes = {
source: SourceNode,
processor: ProcessorNode,
sink: SinkNode,
};
Adding a New Node Type
- Create component in
components/pipeline/nodes/:
import { Handle, Position } from '@xyflow/react';
export function MyCustomNode({ data }) {
return (
<div className="rounded-lg border bg-card p-3 shadow-md">
<Handle type="target" position={Position.Left} />
<div className="text-sm font-medium">{data.label}</div>
<Handle type="source" position={Position.Right} />
</div>
);
}
- Register in
nodeTypesobject inPipelineBuilder.tsx:
const nodeTypes = {
source: SourceNode,
processor: ProcessorNode,
sink: SinkNode,
myCustom: MyCustomNode, // Add here
};
- Add to
NodePalettefor drag-and-drop:
<PaletteItem type="myCustom" label="My Custom Node" icon={<MyIcon />} />
State Management
Zustand (Client State)
Used for UI state, auth, and local preferences:
import { create } from 'zustand';
interface PipelineStore {
selectedNodeId: string | null;
setSelectedNode: (id: string | null) => void;
}
export const usePipelineStore = create<PipelineStore>((set) => ({
selectedNodeId: null,
setSelectedNode: (id) => set({ selectedNodeId: id }),
}));
TanStack Query (Server State)
Used for all API data fetching, caching, and mutations:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function usePipelines() {
return useQuery({
queryKey: ['pipelines'],
queryFn: () => pipelineService.list(),
});
}
export function useStartPipeline() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => pipelineService.start(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['pipelines'] }),
});
}
Real-time Updates
Socket.IO client connects for live data:
import { io } from 'socket.io-client';
const socket = io('/pipelines');
socket.on('pipeline:status', (data) => {
queryClient.setQueryData(['pipelines', data.id], (old) => ({
...old,
status: data.status,
}));
});
Running Tests
npm test # Run all tests (Jest)
npm test -- --watch # Watch mode
npm test -- path/to/file.test.tsx # Single file
Build
npm run dev # Webpack dev server @ localhost:3001
npm run build # Production build
npm run lint # ESLint (src/**/*.ts,tsx)