docs: add Phase 0 documentation
- docs/ARCHITECTURE.md - Clean Architecture patterns and conventions - docs/API_INTEGRATION.md - Core API and Tatlock API endpoints - docs/DEPLOYMENT.md - Docker, NPM, Portainer setup - docs/DATAGRID.md - DataGrid component specification - docs/THEMING.md - Material 3 theming guide Also update seed color to teal for consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
# API Integration Guide
|
||||
|
||||
This document describes the backend APIs that Tatlock UI integrates with.
|
||||
|
||||
## Backend Services
|
||||
|
||||
| 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 - requests must include a valid Bearer token.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Authentik OIDC Flow
|
||||
|
||||
Tatlock UI uses the Authorization Code flow with PKCE:
|
||||
|
||||
1. User clicks "Sign In"
|
||||
2. App redirects to Authentik authorization endpoint
|
||||
3. User authenticates with Authentik
|
||||
4. Authentik redirects back with authorization code
|
||||
5. App exchanges code for tokens
|
||||
6. Access token used for API requests, refresh token for renewal
|
||||
|
||||
### Configuration
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Provider | Authentik |
|
||||
| Client ID | `tatlock-ui` |
|
||||
| Client Type | Public (PKCE) |
|
||||
| Scopes | `openid profile email` |
|
||||
| Discovery URL | `https://auth.schweitz.net/application/o/tatlock-ui/.well-known/openid-configuration` |
|
||||
|
||||
### Token Usage
|
||||
|
||||
```dart
|
||||
// Include in all API requests
|
||||
headers: {
|
||||
'Authorization': 'Bearer $accessToken',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core API Endpoints
|
||||
|
||||
Base URL: `https://api.schweitz.net`
|
||||
|
||||
### Infrastructure
|
||||
|
||||
#### Get System Metrics
|
||||
```
|
||||
GET /infrastructure/resources/system
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"cpu": {
|
||||
"percent": 23.5,
|
||||
"cores": 8
|
||||
},
|
||||
"memory": {
|
||||
"percent": 45.2,
|
||||
"total_gb": 32.0,
|
||||
"used_gb": 14.5
|
||||
},
|
||||
"disk": {
|
||||
"percent": 67.8,
|
||||
"total_gb": 500.0,
|
||||
"used_gb": 339.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### List Containers
|
||||
```
|
||||
GET /infrastructure/containers
|
||||
```
|
||||
|
||||
Query params:
|
||||
- `status` (optional): Filter by status (running, stopped, paused)
|
||||
- `search` (optional): Search by name
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "abc123...",
|
||||
"name": "tatlock-api",
|
||||
"status": "running",
|
||||
"image": "ghcr.io/jpmschweitzer/tatlock:latest",
|
||||
"created": "2024-12-01T10:00:00Z",
|
||||
"ports": ["8000:8000"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Get Container Details
|
||||
```
|
||||
GET /infrastructure/containers/{id}
|
||||
```
|
||||
|
||||
#### Get Container Logs
|
||||
```
|
||||
GET /infrastructure/containers/{id}/logs
|
||||
```
|
||||
|
||||
Query params:
|
||||
- `tail` (optional): Number of lines (default: 100)
|
||||
- `since` (optional): ISO timestamp
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"logs": "2024-12-30 10:00:00 INFO Starting server...\n..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Container Actions
|
||||
```
|
||||
POST /infrastructure/containers/{id}/{action}
|
||||
```
|
||||
|
||||
Actions: `start`, `stop`, `restart`, `pause`, `unpause`
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Container restarted"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Container Resources
|
||||
```
|
||||
GET /infrastructure/resources/containers
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "abc123...",
|
||||
"name": "tatlock-api",
|
||||
"cpu_percent": 2.5,
|
||||
"memory_mb": 256,
|
||||
"memory_limit_mb": 1024
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Dashboard
|
||||
|
||||
#### Get Widget Data
|
||||
```
|
||||
GET /infrastructure/widget-data
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "Infrastructure",
|
||||
"services": [
|
||||
{
|
||||
"name": "Portainer",
|
||||
"url": "https://portainer.schweitz.net",
|
||||
"icon": "portainer",
|
||||
"status": "up"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Health Check
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-12-30T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Housekeeping (Home Assistant)
|
||||
|
||||
#### List Devices
|
||||
```
|
||||
GET /housekeeping/devices
|
||||
```
|
||||
|
||||
Query params:
|
||||
- `area` (optional): Filter by area name
|
||||
- `domain` (optional): Filter by domain (light, switch, climate, etc.)
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"entity_id": "light.living_room",
|
||||
"friendly_name": "Living Room Light",
|
||||
"domain": "light",
|
||||
"state": "on",
|
||||
"area": "Living Room",
|
||||
"attributes": {
|
||||
"brightness": 255,
|
||||
"color_temp": 370
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### List Areas
|
||||
```
|
||||
GET /housekeeping/areas
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "living_room",
|
||||
"name": "Living Room",
|
||||
"device_count": 5
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### List Scenes
|
||||
```
|
||||
GET /housekeeping/scenes
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"entity_id": "scene.movie_time",
|
||||
"friendly_name": "Movie Time",
|
||||
"area": "Living Room"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Control Device
|
||||
```
|
||||
POST /housekeeping/devices/{entity_id}/control
|
||||
```
|
||||
|
||||
Request:
|
||||
```json
|
||||
{
|
||||
"action": "turn_on",
|
||||
"attributes": {
|
||||
"brightness": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Activate Scene
|
||||
```
|
||||
POST /housekeeping/scenes/{scene_id}/activate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tatlock API Endpoints
|
||||
|
||||
Base URL: `https://tatlock.schweitz.net`
|
||||
|
||||
### Chat Completions (Streaming)
|
||||
|
||||
```
|
||||
POST /v1/chat/completions
|
||||
```
|
||||
|
||||
Request:
|
||||
```json
|
||||
{
|
||||
"model": "tatlock",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are Tatlock, a helpful butler."},
|
||||
{"role": "user", "content": "What's the weather like?"}
|
||||
],
|
||||
"stream": true
|
||||
}
|
||||
```
|
||||
|
||||
Response (SSE stream):
|
||||
```
|
||||
data: {"id":"chatcmpl-123","choices":[{"delta":{"content":"The"}}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","choices":[{"delta":{"content":" weather"}}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","choices":[{"delta":{"reasoning_content":"Checking weather API..."}}]}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Important fields:**
|
||||
- `delta.content` - Main response text
|
||||
- `delta.reasoning_content` - Thinking/reasoning (show in collapsible block)
|
||||
|
||||
### List Models
|
||||
|
||||
```
|
||||
GET /v1/models
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "tatlock",
|
||||
"object": "model",
|
||||
"owned_by": "local"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSE Streaming Implementation
|
||||
|
||||
For chat completions, use Server-Sent Events:
|
||||
|
||||
```dart
|
||||
// Platform-aware SSE client
|
||||
class SseClient {
|
||||
Stream<ChatCompletionChunk> streamCompletion(ChatCompletionRequest request) async* {
|
||||
final response = await _client.post(
|
||||
'/v1/chat/completions',
|
||||
data: request.toJson(),
|
||||
options: Options(
|
||||
responseType: ResponseType.stream,
|
||||
headers: {'Accept': 'text/event-stream'},
|
||||
),
|
||||
);
|
||||
|
||||
await for (final chunk in response.data.stream) {
|
||||
final lines = utf8.decode(chunk).split('\n');
|
||||
for (final line in lines) {
|
||||
if (line.startsWith('data: ') && line != 'data: [DONE]') {
|
||||
final json = jsonDecode(line.substring(6));
|
||||
yield ChatCompletionChunk.fromJson(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Standard Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "CONTAINER_NOT_FOUND",
|
||||
"message": "Container with ID 'xyz' not found",
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Status Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 200 | Success |
|
||||
| 400 | Bad Request - Invalid parameters |
|
||||
| 401 | Unauthorized - Token expired or invalid |
|
||||
| 403 | Forbidden - Insufficient permissions |
|
||||
| 404 | Not Found - Resource doesn't exist |
|
||||
| 500 | Server Error - Backend issue |
|
||||
|
||||
### Handling in App
|
||||
|
||||
```dart
|
||||
class ApiException implements Exception {
|
||||
ApiException({required this.code, required this.message});
|
||||
|
||||
final String code;
|
||||
final String message;
|
||||
}
|
||||
|
||||
// In interceptor
|
||||
if (response.statusCode == 401) {
|
||||
// Trigger token refresh or re-auth
|
||||
throw AuthException();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Currently no rate limiting on internal APIs. For LLM endpoints, be mindful of:
|
||||
- Concurrent requests (limit to 1 active chat stream)
|
||||
- Token consumption (context window limits)
|
||||
|
||||
---
|
||||
|
||||
## API Documentation
|
||||
|
||||
Interactive API docs available at:
|
||||
- Core API: https://api.schweitz.net/docs
|
||||
- Tatlock API: https://tatlock.schweitz.net/docs
|
||||
Reference in New Issue
Block a user