commit e0874bb4d12a65e442d2803ab658cec8883e13a0 Author: Jeroen Schweitzer Date: Tue Dec 30 15:23:42 2025 +0100 chore: initial project setup with documentation - Add PHILOSOPHY.md with UI vision and principles - Add README.md with project overview and operational details - Add AGENTS.md with LLM agent instructions - Add PLAN.md with implementation roadmap - Add .gitignore for Flutter project πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24de145 --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# Flutter/Dart +.dart_tool/ +.packages +build/ +.pub-cache/ +.pub/ +pubspec.lock + +# Generated files +*.g.dart +*.freezed.dart +*.gr.dart +*.mocks.dart +lib/generated_plugin_registrant.dart + +# IDE +.idea/ +*.iml +*.ipr +*.iws +.vscode/ +*.swp +*.swo +*~ + +# macOS +.DS_Store +*.dmg +*.plist.backup + +# Android +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.* +**/android/key.properties +*.jks + +# iOS/macOS +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS specific +**/macos/Flutter/ephemeral +**/macos/Flutter/Flutter-Debug.xcconfig +**/macos/Flutter/Flutter-Release.xcconfig +**/macos/Flutter/Flutter-Profile.xcconfig +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Pods/ + +# Linux +**/linux/flutter/ephemeral + +# Windows +**/windows/flutter/ephemeral + +# Web +**/web/.dart_tool/ + +# Coverage +coverage/ +*.lcov + +# Test +.test_coverage.dart + +# Logs +*.log + +# Environment +.env +.env.* +!.env.example + +# Secrets (never commit) +secrets/ +*.pem +*.p12 +*.key + +# Temporary files +*.tmp +*.temp +.temp/ + +# Uploads (reference images, not tracked) +uploads/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d13b81c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# LLM Agent Instructions + +This document contains instructions and documentation references for AI assistants working with this codebase. + +> **πŸ“– Important**: Before working on this project, read [PHILOSOPHY.md](PHILOSOPHY.md) to understand the system vision, architectural patterns, and design goals. All development should work towards realizing those patterns. +# AGENTS.md + +> **Start every session by reading this file.** +> This file outlines the operational protocols, coding standards, and architectural decisions for this FastAPI project. + +## 1. Agent Operational Protocols + +### 🧠 Work Patterns (Plan-Act-Reflect) +* **Plan:** Before writing code, briefly outline your plan. Identify which files you will touch and what the side effects might be. +* **Act:** Execute the changes in small, atomic steps. +* **Reflect:** After coding, verify your work. Did you break existing tests? Did you add new tests? + +### 🌐 Internal Service Access +* **git.schweitz.net**: Access via `http://localhost:3002` (direct Gitea) to bypass Authentik SSO + * Example: `curl http://localhost:3002/jpmschweitzer/library-desk/raw/branch/main/README.md` + * Public repos are readable without authentication + * Related repos: , `core-api`, `tatlock`, `library-desk`, `scheduler`, `portainer-core` + +### 🐳 Deployment & Infrastructure +* **Full stack documentation**: Available in the `portainer-core` repo + * Access: `curl http://localhost:3002/jpmschweitzer/portainer-core/raw/branch/main/CONTAINERS.md` + * Contains: All service ports, URLs, Redis DB allocations, external domains +* **Tatlock deployment**: + * LAN: `http://192.168.86.149:8000` + * External: `tatlock.schweitz.net` (behind Authentik SSO) + * Redis DBs: 1 (memory), 6 (benchmarks) +* **Health check**: `curl http://192.168.86.149:8000/health` + +### πŸ›‘οΈ Git Discipline +* **NEVER commit to `main` or `master` directly.** Always create a feature branch: `feature/your-feature-name` or `fix/issue-description`. +* **Commit Messages:** Use the [Conventional Commits](https://www.conventionalcommits.org/) format. + * `feat: add user login endpoint` + * `fix: resolve database connection timeout` + * `refactor: split monolith dependency file` +* **Atomic Commits:** Keep commits small. One logical change = one commit. + +### πŸ“ Changelog Maintenance +* **Update `CHANGELOG.md`** with every user-facing change. +* Format: `## [Unreleased] - YYYY-MM-DD` followed by `### Added`, `### Changed`, or `### Fixed`. \ No newline at end of file diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md new file mode 100644 index 0000000..c544e5f --- /dev/null +++ b/PHILOSOPHY.md @@ -0,0 +1,117 @@ +# Tatlock UI - Philosophy + +## Document Purpose + +This document establishes the foundational philosophy for **Tatlock UI**, the visual interface to the Tatlock homelab ecosystem. It represents the **north star** that all UI development should work towards. + +**When to modify this document**: +- When there is a deliberate decision to change the UI's fundamental purpose or principles +- When the relationship between UI and backend fundamentally changes +- When new insights require rethinking core design philosophy + +**When NOT to modify this document**: +- For implementation details (use README.md or code comments) +- For technical specifications (use docs/ folder) +- For tactical decisions about specific libraries or patterns + +--- + +## Vision + +Tatlock UI is the **window into the household**β€”the visual interface through which users interact with the Tatlock ecosystem. Just as the Tatlock backend embodies a capable British butler coordinating a household staff, the UI represents the **front hall and drawing room** where guests are received and served. + +The interface should feel like stepping into a well-appointed estate: organized, capable, and quietly impressive. Users don't need to understand the complexity of the household operations happening behind the scenesβ€”they simply make requests and receive excellent service. + +--- + +## Core Principles + +### 1. The Butler's Discretion + +The UI presents information with **considered restraint**. Like a butler who knows when to speak and when to remain silent, the interface shows what's relevant without overwhelming. Dashboard widgets appear when useful; notifications arise only when warranted; complexity is hidden until needed. + +### 2. Unified Command + +All household operationsβ€”infrastructure monitoring, home automation, AI conversations, service managementβ€”flow through a **single, coherent interface**. Users shouldn't need multiple dashboards or tools. Tatlock UI is the one place to manage the estate. + +### 3. Transparent Operations + +When the household is working on a request, users should see that activity. The UI makes the **invisible visible**: streaming AI responses show reasoning in real-time, long-running operations display progress, and system state is always apparent at a glance. + +### 4. Adaptive Presence + +The interface adapts to its context. On a wide desktop monitor, it spreads comfortably with sidebar navigation. On a phone, it condenses to essential controls. On a wall-mounted tablet, it becomes an ambient dashboard. Same household, different rooms. + +### 5. Privacy by Architecture + +The UI connects only to local services within the homelab. No external analytics, no cloud dependencies for core functionality, no data leaving the estate. Users own their interface as completely as they own their data. + +--- + +## The Interface Metaphor + +### Rooms of the Estate + +The navigation structure reflects areas of the household: + +- **The Front Hall** (Dashboard): First impression, overview of estate status +- **The Study** (Tatlock Chat): Private conversation with the butler +- **The Control Room** (Infrastructure): Where the Handyman monitors systems +- **The Parlor** (Housekeeping): Home automation and environmental controls +- **The Library** (future): Knowledge management and documentation +- **The Office** (future): Scheduling, tasks, and organization + +### Visual Language + +The aesthetic should evoke **quiet competence**: +- Clean lines and purposeful spacing +- Information density that respects attention +- Color used meaningfully, not decoratively +- Typography that's readable at a glance +- Motion that informs rather than entertains + +--- + +## Success Criteria + +Tatlock UI succeeds when: + +1. **It becomes the default**: Users naturally open Tatlock UI first when they want to check on, control, or interact with anything in their homelab + +2. **It feels effortless**: Common tasks require minimal clicks; information is where users expect it; the interface anticipates needs + +3. **It builds confidence**: Users trust what they see; system state is accurate; actions have clear feedback + +4. **It scales gracefully**: Works equally well for a quick status check or an extended chat session; handles growth in services and capabilities + +5. **It respects attention**: Never demands focus unnecessarily; stays useful in the background; surfaces only what matters + +--- + +## Relationship to the Household + +Tatlock UI is a **client** of the Tatlock ecosystem, not the system itself. The intelligence, coordination, and household metaphor live in the backend services. The UI's job is to: + +- Present the household's capabilities clearly +- Transmit user intentions accurately +- Display responses and state faithfully +- Stay out of the way of the actual work + +The butler (backend) runs the household. The UI opens the door. + +--- + +## Document Metadata + +**Document Type**: Philosophical Foundation (Stable) +**Version**: 1.0 +**Established**: 2024-12-30 + +**Related Documents**: +- **README.md**: Project setup and operational details +- **AGENTS.md**: LLM agent development guidelines +- **PLAN.md**: Implementation roadmap and phases + +--- + +*The best interface is one you don't noticeβ€”it simply works.* diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..fcaf9b8 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,670 @@ +# Tatlock UI - Comprehensive Implementation Plan + +> **Session Continuity Note**: This document is designed to be self-contained. If starting a new session, read this entire document to understand the project scope and decisions made. + +## Project Summary + +**Tatlock UI** is a Flutter application that will serve as the unified homelab dashboard for the Tower of Joy infrastructure, replacing Organizr at `home.schweitz.net`. + +### Key Facts +- **Project folder**: `/mnt/media/Projects/tatlock-ui` (renamed from tower-ui) +- **Git remote**: `ssh://git@git.schweitz.net:2222/jpmschweitzer/tatlock-ui.git` (already created) +- **Platforms**: web (primary), android, macos, linux, windows, ios +- **Primary backend**: Core API (port 8083) + Tatlock API (port 8000) +- **Authentication**: Authentik SSO via OIDC +- **Web deployment**: Docker container on port 8092, proxied via NPM at `home.schweitz.net` + +### What It Replaces +- **Organizr** (current dashboard at home.schweitz.net) - See `/mnt/media/Projects/tower-ui/uploads/portainer-ui.png` for current UI +- **Eventually Open WebUI** - Custom LLM chat interface for Tatlock agents + +--- + +## Architecture Decisions (Final) + +### 1. Dogmatic Clean Architecture +**Decision**: Use strict Clean Architecture with documented requirements for consistency. + +``` +lib/features/{feature}/ +β”œβ”€β”€ presentation/ # UI Layer +β”‚ β”œβ”€β”€ pages/ # Full screen widgets +β”‚ β”œβ”€β”€ widgets/ # Feature-specific widgets +β”‚ └── providers/ # Riverpod providers/controllers +β”œβ”€β”€ domain/ # Business Logic Layer +β”‚ β”œβ”€β”€ entities/ # Business objects (immutable) +β”‚ β”œβ”€β”€ repositories/ # Abstract repository interfaces +β”‚ └── usecases/ # Single-purpose business operations +└── data/ # Data Layer + β”œβ”€β”€ models/ # JSON serializable DTOs + β”œβ”€β”€ datasources/ # API clients, local storage + └── repositories/ # Repository implementations +``` + +**Rules**: +- Domain layer has NO dependencies on Flutter or external packages +- Data layer implements domain interfaces +- Presentation layer only depends on domain layer +- All cross-layer communication via dependency injection (Riverpod) + +### 2. DataGrid Component System +**Decision**: Adapt the fframe ListGrid pattern for consistent table UIs across all features. + +Based on: `https://github.com/postmeridiem/fframe/blob/main/fframe/lib/screens/listgrid_screen/` + +Key components: +- `DataGridConfig` - Declarative configuration +- `DataGridColumn` - Column definitions with builders +- `DataGridAction` - Per-row actions +- `DataGridBulkAction` - Multi-select actions +- `DataGridSource` - Abstract data source (Core API adapter) +- `DataGridNotifier` - Riverpod state management + +### 3. Theming +**Decision**: System preference + manual override, using Material3 colorScheme exclusively. + +- Follow system dark/light mode by default +- Allow manual override that persists to SharedPreferences +- Never use raw colors in widgets - always `Theme.of(context).colorScheme.*` +- Use automatic theme generator (flex_color_scheme or similar) + +### 4. State Management +**Decision**: Riverpod 2.x with code generation. + +Packages: `flutter_riverpod`, `riverpod_annotation`, `riverpod_generator` + +--- + +## Implementation Phases + +### Phase 0: Documentation +**Goal**: Establish project documentation before any code. + +**Files to create**: +``` +tatlock-ui/ +β”œβ”€β”€ README.md # Project overview, setup instructions +β”œβ”€β”€ AGENTS.md # LLM agent instructions (copy pattern from Tatlock) +β”œβ”€β”€ CHANGELOG.md # Version history (start with 0.0.1) +β”œβ”€β”€ docs/ +β”‚ β”œβ”€β”€ ARCHITECTURE.md # Clean Architecture rules and patterns +β”‚ β”œβ”€β”€ API_INTEGRATION.md # Core API and Tatlock API endpoints +β”‚ β”œβ”€β”€ DEPLOYMENT.md # Docker, NPM, Portainer setup +β”‚ β”œβ”€β”€ DATAGRID.md # DataGrid component API specification +β”‚ └── THEMING.md # Theme system documentation +``` + +**Content for ARCHITECTURE.md** - Document: +- Clean Architecture layer rules +- File naming conventions +- Dependency injection patterns +- Testing requirements per layer + +**Content for API_INTEGRATION.md** - Document: +- Core API endpoints (from `/mnt/media/Projects/portainer-core/CONTAINERS.md`) +- Tatlock API endpoints (chat completions, responses API) +- Authentication flow with Authentik +- Error handling patterns + +**Content for DATAGRID.md** - Document: +- Full DataGrid API spec (see below) +- Usage examples for each feature +- Customization patterns + +### Phase 1: Foundation +**Goal**: Project setup, core infrastructure, basic shell. + +**Tasks**: +1. Initialize Flutter project with all platforms +2. Initialize git and push to Gitea +3. Set up project structure (Clean Architecture folders) +4. Configure dependencies (pubspec.yaml) +5. Implement core infrastructure: + - `lib/core/api/api_client.dart` - Dio HTTP client + - `lib/core/api/api_interceptors.dart` - Auth, logging, error handling + - `lib/core/auth/auth_service.dart` - Authentik OIDC + - `lib/core/auth/auth_provider.dart` - Riverpod auth state + - `lib/core/config/app_config.dart` - Environment configuration + - `lib/core/config/service_locator.dart` - Dependency setup + - `lib/core/theme/app_theme.dart` - Material3 theme + - `lib/core/theme/theme_provider.dart` - Theme state (system + override) +6. Create app shell: + - `lib/app.dart` - MaterialApp.router setup + - `lib/routing/app_router.dart` - go_router configuration + - `lib/shared/layouts/app_scaffold.dart` - Main layout with sidebar + - `lib/shared/layouts/adaptive_navigation.dart` - Responsive nav +7. Set up CI/CD: + - `.gitea/workflows/test.yml` + - `.gitea/workflows/build-web.yml` + - `Dockerfile` + +### Phase 2: DataGrid Component +**Goal**: Build the reusable DataGrid system before features. + +**Files to create**: +``` +lib/shared/components/data_grid/ +β”œβ”€β”€ data_grid.dart # Main widget +β”œβ”€β”€ data_grid_config.dart # Configuration classes +β”œβ”€β”€ data_grid_column.dart # Column definition +β”œβ”€β”€ data_grid_action.dart # Action definitions +β”œβ”€β”€ data_grid_source.dart # Abstract data source +β”œβ”€β”€ data_grid_provider.dart # Riverpod state +β”œβ”€β”€ data_grid_state.dart # Freezed state class +β”œβ”€β”€ widgets/ +β”‚ β”œβ”€β”€ data_grid_header.dart # Header row with sort indicators +β”‚ β”œβ”€β”€ data_grid_row.dart # Data row +β”‚ β”œβ”€β”€ data_grid_cell.dart # Cell wrapper +β”‚ β”œβ”€β”€ data_grid_checkbox.dart # Selection checkbox +β”‚ β”œβ”€β”€ data_grid_actions_menu.dart # Row actions popup +β”‚ β”œβ”€β”€ data_grid_bulk_actions.dart # Bulk action bar +β”‚ β”œβ”€β”€ data_grid_search_bar.dart # Search input +β”‚ β”œβ”€β”€ data_grid_footer.dart # Footer with count/pagination +β”‚ └── data_grid_empty_state.dart # Empty state display +└── adapters/ + └── core_api_source.dart # Core API data source adapter +``` + +### Phase 3: Dashboard (Organizr Replacement) +**Goal**: Replace Organizr homepage with Tatlock UI dashboard. + +**Features to match** (from portainer-ui.png): +- Sidebar navigation with grouped items +- System metrics gauges (CPU, Memory, Disk) +- Weather widget (optional - requires API) +- Air quality widget (optional) +- Service quick links grid + +**Files to create**: +``` +lib/features/dashboard/ +β”œβ”€β”€ presentation/ +β”‚ β”œβ”€β”€ pages/dashboard_page.dart +β”‚ └── widgets/ +β”‚ β”œβ”€β”€ metrics_gauge.dart +β”‚ β”œβ”€β”€ service_card.dart +β”‚ β”œβ”€β”€ service_grid.dart +β”‚ └── quick_actions.dart +β”œβ”€β”€ domain/ +β”‚ β”œβ”€β”€ entities/system_metrics.dart +β”‚ └── repositories/dashboard_repository.dart +└── data/ + β”œβ”€β”€ datasources/core_api_dashboard_source.dart + └── repositories/dashboard_repository_impl.dart +``` + +**Core API endpoints used**: +- `GET /infrastructure/resources/system` - System metrics +- `GET /infrastructure/widget-data` - Services + groups +- `GET /health` - Overall health status + +### Phase 4: Container Management +**Goal**: Full container CRUD and monitoring. + +**Files to create**: +``` +lib/features/containers/ +β”œβ”€β”€ presentation/ +β”‚ β”œβ”€β”€ pages/ +β”‚ β”‚ β”œβ”€β”€ containers_list_page.dart +β”‚ β”‚ └── container_detail_page.dart +β”‚ β”œβ”€β”€ widgets/ +β”‚ β”‚ β”œβ”€β”€ container_status_badge.dart +β”‚ β”‚ β”œβ”€β”€ container_logs_viewer.dart +β”‚ β”‚ └── container_resource_chart.dart +β”‚ └── providers/ +β”‚ └── containers_provider.dart +β”œβ”€β”€ domain/ +β”‚ β”œβ”€β”€ entities/container.dart +β”‚ β”œβ”€β”€ repositories/container_repository.dart +β”‚ └── usecases/ +β”‚ β”œβ”€β”€ get_containers.dart +β”‚ β”œβ”€β”€ start_container.dart +β”‚ β”œβ”€β”€ stop_container.dart +β”‚ β”œβ”€β”€ restart_container.dart +β”‚ └── get_container_logs.dart +└── data/ + β”œβ”€β”€ models/container_model.dart + β”œβ”€β”€ datasources/containers_datasource.dart + └── repositories/container_repository_impl.dart +``` + +**Core API endpoints used**: +- `GET /infrastructure/containers` +- `GET /infrastructure/containers/{id}` +- `GET /infrastructure/containers/{id}/logs` +- `POST /infrastructure/containers/{id}/{action}` +- `GET /infrastructure/resources/containers` + +### Phase 5: LLM Chat Interface +**Goal**: Custom chat UI for Tatlock agents with SSE streaming. + +**Files to create**: +``` +lib/core/api/ +└── sse_client.dart # Platform-aware SSE streaming + +lib/features/chat/ +β”œβ”€β”€ presentation/ +β”‚ β”œβ”€β”€ pages/chat_page.dart +β”‚ └── widgets/ +β”‚ β”œβ”€β”€ message_list.dart +β”‚ β”œβ”€β”€ message_bubble.dart +β”‚ β”œβ”€β”€ reasoning_block.dart # Collapsible think blocks +β”‚ β”œβ”€β”€ streaming_text.dart +β”‚ β”œβ”€β”€ chat_input.dart +β”‚ β”œβ”€β”€ model_selector.dart +β”‚ └── agent_indicator.dart # Shows which agent is responding +β”œβ”€β”€ domain/ +β”‚ β”œβ”€β”€ entities/ +β”‚ β”‚ β”œβ”€β”€ message.dart +β”‚ β”‚ β”œβ”€β”€ conversation.dart +β”‚ β”‚ └── chat_completion.dart +β”‚ └── repositories/chat_repository.dart +└── data/ + β”œβ”€β”€ models/ + β”‚ β”œβ”€β”€ chat_completion_request.dart + β”‚ β”œβ”€β”€ chat_completion_response.dart + β”‚ └── chat_completion_chunk.dart + β”œβ”€β”€ datasources/tatlock_api_datasource.dart + └── repositories/chat_repository_impl.dart +``` + +**Tatlock API endpoints used**: +- `POST /v1/chat/completions` (with `stream: true`) +- `GET /v1/models` + +**SSE Implementation Notes**: +- Use platform-conditional imports +- Web: XMLHttpRequest or fetch API +- Mobile/Desktop: eventsource or http package with stream parsing +- Handle `reasoning_content` field for think blocks (DeepSeek R1 format) + +### Phase 6: Home Automation +**Goal**: Control Home Assistant via Core API. + +**Files to create**: +``` +lib/features/housekeeping/ +β”œβ”€β”€ presentation/ +β”‚ β”œβ”€β”€ pages/ +β”‚ β”‚ β”œβ”€β”€ housekeeping_page.dart +β”‚ β”‚ └── area_detail_page.dart +β”‚ └── widgets/ +β”‚ β”œβ”€β”€ device_tile.dart +β”‚ β”œβ”€β”€ device_control_sheet.dart +β”‚ β”œβ”€β”€ scene_card.dart +β”‚ └── area_grid.dart +β”œβ”€β”€ domain/ +β”‚ β”œβ”€β”€ entities/ +β”‚ β”‚ β”œβ”€β”€ device.dart +β”‚ β”‚ β”œβ”€β”€ area.dart +β”‚ β”‚ └── scene.dart +β”‚ └── repositories/housekeeping_repository.dart +└── data/ + β”œβ”€β”€ models/ + β”‚ β”œβ”€β”€ device_model.dart + β”‚ └── device_control_request.dart + β”œβ”€β”€ datasources/housekeeping_datasource.dart + └── repositories/housekeeping_repository_impl.dart +``` + +**Core API endpoints used**: +- `GET /housekeeping/devices` +- `GET /housekeeping/areas` +- `GET /housekeeping/scenes` +- `POST /housekeeping/devices/{entity_id}/control` +- `POST /housekeeping/scenes/{scene_id}/activate` + +### Phase 7: Polish & Deployment +**Goal**: Production-ready release. + +**Tasks**: +1. Flutter web optimization (deferred loading, tree shaking) +2. Desktop platform testing and fixes +3. Offline resilience patterns +4. Error handling and user feedback +5. Performance profiling +6. Docker build and Portainer stack deployment +7. NPM proxy configuration update (home.schweitz.net β†’ tatlock-ui) + +--- + +## DataGrid API Specification + +### Configuration Classes + +```dart +/// Main configuration for a data grid +class DataGridConfig { + const DataGridConfig({ + required this.columns, + this.actions = const [], + this.bulkActions = const [], + this.rowsSelectable = false, + this.showHeader = true, + this.showFooter = true, + this.enableSearch = false, + this.searchableColumns = const [], + this.defaultSortColumn, + this.defaultSortDescending = false, + this.emptyStateBuilder, + this.loadingBuilder, + this.errorBuilder, + this.onRowTap, + this.cellPadding = const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + this.headerHeight = 48.0, + this.rowHeight, + this.dataMode = const DataGridDataMode.all(), + }); + + final List> columns; + final List> actions; + final List> bulkActions; + final bool rowsSelectable; + final bool showHeader; + final bool showFooter; + final bool enableSearch; + final List searchableColumns; + final int? defaultSortColumn; + final bool defaultSortDescending; + final Widget Function(BuildContext)? emptyStateBuilder; + final Widget Function(BuildContext)? loadingBuilder; + final Widget Function(BuildContext, Object error)? errorBuilder; + final void Function(T item)? onRowTap; + final EdgeInsetsGeometry cellPadding; + final double headerHeight; + final double? rowHeight; + final DataGridDataMode dataMode; +} + +/// Column definition +class DataGridColumn { + const DataGridColumn({ + required this.header, + required this.valueBuilder, + this.cellBuilder, + this.cellControlsBuilder, + this.width = const DataGridColumnWidth.flex(1), + this.alignment = DataGridColumnAlignment.start, + this.sortable = false, + this.sortField, + this.searchable = false, + this.visible = true, + this.tooltip, + }); + + final String header; + final String Function(T item) valueBuilder; + final Widget Function(BuildContext, T item)? cellBuilder; + final Widget Function(BuildContext, T item)? cellControlsBuilder; + final DataGridColumnWidth width; + final DataGridColumnAlignment alignment; + final bool sortable; + final String? sortField; + final bool searchable; + final bool visible; + final String Function(T item)? tooltip; +} + +/// Column width - sealed class with variants +sealed class DataGridColumnWidth { + const DataGridColumnWidth._(); + const factory DataGridColumnWidth.fixed(double width) = _FixedWidth; + const factory DataGridColumnWidth.flex(int flex) = _FlexWidth; + const factory DataGridColumnWidth.fraction(double fraction) = _FractionWidth; +} + +/// Per-row action +class DataGridAction { + const DataGridAction({ + required this.icon, + required this.label, + required this.onTap, + this.showWhen, + this.destructive = false, + this.requiresConfirmation = false, + this.confirmationMessage, + }); + + final IconData icon; + final String label; + final Future Function(T item) onTap; + final bool Function(T item)? showWhen; + final bool destructive; + final bool requiresConfirmation; + final String? confirmationMessage; +} + +/// Bulk action on selected rows +class DataGridBulkAction { + const DataGridBulkAction({ + required this.icon, + required this.label, + required this.onTap, + this.minSelected = 1, + this.maxSelected, + this.destructive = false, + this.requiresConfirmation = false, + }); + + final IconData icon; + final String label; + final Future Function(List items) onTap; + final int minSelected; + final int? maxSelected; + final bool destructive; + final bool requiresConfirmation; +} + +/// Data loading mode +sealed class DataGridDataMode { + const DataGridDataMode._(); + const factory DataGridDataMode.all() = _AllDataMode; + const factory DataGridDataMode.paginated({int pageSize}) = _PaginatedDataMode; + const factory DataGridDataMode.infinite({int initialLoad, int loadMoreThreshold}) = _InfiniteDataMode; +} +``` + +### Data Source + +```dart +/// Abstract data source +abstract class DataGridSource { + Future> fetch({ + String? searchQuery, + String? sortField, + bool sortDescending = false, + int? offset, + int? limit, + }); + + Future count({String? searchQuery}); +} + +/// Result wrapper +class DataGridResult { + const DataGridResult({ + required this.items, + required this.totalCount, + this.hasMore = false, + }); + + final List items; + final int totalCount; + final bool hasMore; +} + +/// Core API adapter +class CoreApiDataSource extends DataGridSource { + CoreApiDataSource({ + required this.endpoint, + required this.fromJson, + this.searchParam = 'search', + this.sortParam = 'sort', + this.orderParam = 'order', + }); + + final String endpoint; + final T Function(Map) fromJson; + final String searchParam; + final String sortParam; + final String orderParam; +} +``` + +--- + +## Web Deployment Architecture + +``` +Internet β†’ NPM (home.schweitz.net:443) β†’ tatlock-ui container (port 8092) β†’ Flutter web +``` + +**Dockerfile**: +```dockerfile +FROM ghcr.io/cirruslabs/flutter:3.27.0 AS build +WORKDIR /app +COPY pubspec.* ./ +RUN flutter pub get +COPY . . +RUN flutter build web --release + +FROM nginx:alpine +COPY --from=build /app/build/web /usr/share/nginx/html +EXPOSE 80 +``` + +**Portainer Stack (docker-compose.yml)**: +```yaml +version: '3.8' +services: + tatlock-ui: + image: git.schweitz.net/jpmschweitzer/tatlock-ui:latest + container_name: tatlock-ui + restart: unless-stopped + ports: + - "8092:80" + networks: + - docker-dataplane + +networks: + docker-dataplane: + external: true +``` + +**NPM Proxy Configuration**: +- Domain: `home.schweitz.net` +- Forward to: `localhost:8092` +- SSL: Let's Encrypt, Force SSL + +--- + +## Key Dependencies (pubspec.yaml) + +```yaml +dependencies: + flutter: + sdk: flutter + + # State Management + flutter_riverpod: ^2.5.0 + riverpod_annotation: ^2.3.0 + hooks_riverpod: ^2.5.0 + flutter_hooks: ^0.20.0 + + # Code Generation + freezed_annotation: ^2.4.0 + json_annotation: ^4.8.0 + + # Networking + dio: ^5.4.0 + retrofit: ^4.1.0 + + # Authentication + flutter_appauth: ^6.0.0 + flutter_secure_storage: ^9.0.0 + + # Routing + go_router: ^13.0.0 + + # UI + flex_color_scheme: ^7.3.0 + flutter_adaptive_scaffold: ^0.1.0 + flutter_markdown: ^0.6.0 + flutter_highlight: ^0.7.0 + fl_chart: ^0.65.0 + +dev_dependencies: + build_runner: ^2.4.0 + freezed: ^2.4.0 + json_serializable: ^6.7.0 + riverpod_generator: ^2.4.0 + retrofit_generator: ^8.1.0 + mocktail: ^1.0.0 +``` + +--- + +## Reference Documents + +- **Infrastructure**: `/mnt/media/Projects/portainer-core/CONTAINERS.md` +- **Tatlock Philosophy**: `/mnt/media/Projects/tatlock/PHILOSOPHY.md` +- **Core API**: `http://192.168.86.149:8083/docs` +- **Tatlock API**: `http://192.168.86.149:8000/docs` +- **fframe ListGrid**: `https://github.com/postmeridiem/fframe/tree/main/fframe/lib/screens/listgrid_screen` +- **Current Organizr UI**: `/mnt/media/Projects/tower-ui/uploads/portainer-ui.png` +- **Tatlock Logo**: `/mnt/media/Projects/tower-ui/uploads/logo-tatlock.png` + +--- + +## Navigation Structure (matching Organizr) + +``` +β”œβ”€β”€ Homepage (Dashboard) +β”œβ”€β”€ Tatlock (Chat) +β”œβ”€β”€ Jellyfin (external link) +β”œβ”€β”€ SearXNG (external link) +β”œβ”€β”€ AMP +β”‚ └── AMP Home +β”œβ”€β”€ Coding +β”‚ β”œβ”€β”€ Cloud IDE (external) +β”‚ └── Gitea (external) +β”œβ”€β”€ Infrastructure +β”‚ β”œβ”€β”€ Containers (main feature) +β”‚ β”œβ”€β”€ Netdata (external link) +β”‚ β”œβ”€β”€ Portainer (external link) +β”‚ └── Proxy Manager (external link) +β”œβ”€β”€ Housekeeping (Home Automation) +β”œβ”€β”€ API Docs (external link to Core API) +└── Settings +``` + +--- + +## Authentik Configuration + +Create application in Authentik admin: +- **Name**: Tatlock UI +- **Slug**: `tatlock-ui` +- **Provider**: OAuth2/OIDC +- **Client Type**: Public +- **Redirect URIs**: + - `https://home.schweitz.net/callback` + - `net.schweitz.tatlock://callback` + - `http://localhost:*/callback` + +--- + +## Next Steps After Session Resume + +1. **Verify folder rename**: Confirm `/mnt/media/Projects/tatlock-ui` exists +2. **Start Phase 0**: Create documentation files +3. **Initialize Flutter**: `flutter create . --project-name tatlock_ui --org net.schweitz --platforms=web,android,ios,macos,linux,windows` +4. **Initialize Git**: `git init && git remote add origin ssh://git@git.schweitz.net:2222/jpmschweitzer/tatlock-ui.git` +5. **Continue with Phase 1**: Core infrastructure setup + +--- + +*Last updated: 2024-12-30* +*Plan version: 2.0* diff --git a/README.md b/README.md new file mode 100644 index 0000000..b92c1e9 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# Tatlock UI + +A Flutter-based unified homelab dashboard for the Tower of Joy infrastructure, replacing Organizr at `home.schweitz.net`. + +## Overview + +Tatlock UI provides a single interface for: +- **Dashboard**: System metrics, service status, quick actions +- **Tatlock Chat**: LLM conversation interface with streaming responses +- **Infrastructure**: Container management, logs, resource monitoring +- **Housekeeping**: Home automation control via Home Assistant + +See [PHILOSOPHY.md](PHILOSOPHY.md) for the guiding vision. + +## Tech Stack + +| Layer | Technology | +|-------|------------| +| Framework | Flutter 3.38+ | +| State Management | Riverpod 2.x with code generation | +| Routing | go_router | +| HTTP Client | Dio + Retrofit | +| Authentication | Authentik OIDC via flutter_appauth | +| Theming | flex_color_scheme (Material 3) | +| Code Generation | freezed, json_serializable, riverpod_generator | + +## Platforms + +| Platform | Status | Notes | +|----------|--------|-------| +| Web | Primary | Docker deployment at port 8092 | +| Android | Supported | For mobile access | +| iOS | Supported | For mobile access | +| macOS | Supported | Native desktop | +| Linux | Supported | Native desktop | +| Windows | Supported | Native desktop | + +## Architecture + +This project follows **Clean Architecture** with feature-based organization: + +``` +lib/ +β”œβ”€β”€ app.dart # MaterialApp.router setup +β”œβ”€β”€ main.dart # Entry point +β”œβ”€β”€ core/ # Shared infrastructure +β”‚ β”œβ”€β”€ api/ # HTTP client, interceptors, SSE +β”‚ β”œβ”€β”€ auth/ # Authentik OIDC integration +β”‚ β”œβ”€β”€ config/ # Environment, service locator +β”‚ └── theme/ # Material 3 theming +β”œβ”€β”€ routing/ # go_router configuration +β”œβ”€β”€ shared/ # Reusable components +β”‚ β”œβ”€β”€ components/ # DataGrid, widgets +β”‚ └── layouts/ # App scaffold, navigation +└── features/ # Feature modules + β”œβ”€β”€ dashboard/ + β”œβ”€β”€ chat/ + β”œβ”€β”€ containers/ + └── housekeeping/ +``` + +Each feature follows the three-layer pattern: + +``` +features/{feature}/ +β”œβ”€β”€ presentation/ # UI Layer +β”‚ β”œβ”€β”€ pages/ # Full screen widgets +β”‚ β”œβ”€β”€ widgets/ # Feature-specific widgets +β”‚ └── providers/ # Riverpod providers +β”œβ”€β”€ domain/ # Business Logic Layer +β”‚ β”œβ”€β”€ entities/ # Immutable business objects +β”‚ β”œβ”€β”€ repositories/ # Abstract interfaces +β”‚ └── usecases/ # Single-purpose operations +└── data/ # Data Layer + β”œβ”€β”€ models/ # JSON serializable DTOs + β”œβ”€β”€ datasources/ # API clients + └── repositories/ # Repository implementations +``` + +**Layer Rules**: +- Domain layer has NO dependencies on Flutter or external packages +- Data layer implements domain interfaces +- Presentation layer only depends on domain layer +- Cross-layer communication via dependency injection (Riverpod) + +## Backend APIs + +| Service | URL | Purpose | +|---------|-----|---------| +| Core API | `https://api.schweitz.net` | Infrastructure, widgets, housekeeping | +| Tatlock API | `https://tatlock.schweitz.net` | LLM chat completions, streaming | + +Both APIs are behind Authentik SSO. + +**Key Endpoints**: +- `GET /infrastructure/containers` - Container list and management +- `GET /infrastructure/resources/system` - CPU, memory, disk metrics +- `GET /housekeeping/devices` - Home Assistant devices +- `POST /v1/chat/completions` - Tatlock LLM (SSE streaming) + +## Development Setup + +### Prerequisites + +- Flutter SDK 3.38+ +- Dart SDK 3.10+ +- Java 17+ (required for Android builds with Gradle 8.14+) +- For mobile: Android Studio / Xcode 26+ +- For desktop: Platform-specific toolchains + +### Getting Started + +```bash +# Clone the repository +git clone ssh://git@git.schweitz.net:2222/jpmschweitzer/tatlock-ui.git +cd tatlock-ui + +# Install dependencies +flutter pub get + +# Generate code (freezed, json_serializable, riverpod) +dart run build_runner build --delete-conflicting-outputs + +# Run development server +flutter run -d chrome # Web +flutter run -d macos # macOS +flutter run # Default device +``` + +### Code Generation + +After modifying any `@freezed`, `@JsonSerializable`, or `@riverpod` annotated code: + +```bash +dart run build_runner build --delete-conflicting-outputs + +# Or watch mode during development +dart run build_runner watch --delete-conflicting-outputs +``` + +## Deployment + +### Web (Docker) + +```bash +# Build the image +docker build -t git.schweitz.net/jpmschweitzer/tatlock-ui:latest . + +# Or use the Gitea CI/CD pipeline +git push origin main +``` + +**Deployment target**: Port 8092, proxied via NPM at `home.schweitz.net` + +### Portainer Stack + +```yaml +version: '3.8' +services: + tatlock-ui: + image: git.schweitz.net/jpmschweitzer/tatlock-ui:latest + container_name: tatlock-ui + restart: unless-stopped + ports: + - "8092:80" + networks: + - docker-dataplane + +networks: + docker-dataplane: + external: true +``` + +## Navigation Structure + +``` +β”œβ”€β”€ Homepage (Dashboard) +β”œβ”€β”€ Tatlock (Chat) +β”œβ”€β”€ Jellyfin (external) +β”œβ”€β”€ SearXNG (external) +β”œβ”€β”€ AMP +β”‚ └── AMP Home +β”œβ”€β”€ Coding +β”‚ β”œβ”€β”€ Cloud IDE (external) +β”‚ └── Gitea (external) +β”œβ”€β”€ Infrastructure +β”‚ β”œβ”€β”€ Containers +β”‚ β”œβ”€β”€ Netdata (external) +β”‚ β”œβ”€β”€ Portainer (external) +β”‚ └── Proxy Manager (external) +β”œβ”€β”€ Housekeeping +β”œβ”€β”€ API Docs (external) +└── Settings +``` + +## Authentication + +Uses Authentik OIDC with the following configuration: + +| Setting | Value | +|---------|-------| +| Application | `tatlock-ui` | +| Client Type | Public | +| Redirect URIs | `https://home.schweitz.net/callback`, `net.schweitz.tatlock://callback`, `http://localhost:*/callback` | + +## Project Links + +- **Repository**: [git.schweitz.net/jpmschweitzer/tatlock-ui](https://git.schweitz.net/jpmschweitzer/tatlock-ui) +- **Deployment**: [home.schweitz.net](https://home.schweitz.net) (replaces Organizr) +- **Core API Docs**: [api.schweitz.net/docs](https://api.schweitz.net/docs) +- **Tatlock API Docs**: [household.schweitz.net/docs](https://household.schweitz.net/docs) + +## Related Repositories + +- **tatlock**: Backend LLM orchestration service +- **core-api**: Infrastructure and housekeeping API +- **portainer-core**: Docker Swarm stack definitions + +## License + +Private - All rights reserved