diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..44dbd4b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "mcp__chrome-devtools__take_screenshot", + "mcp__puppeteer__puppeteer_evaluate", + "mcp__chrome-devtools__navigate_page" + ] + } +} diff --git a/.gitignore b/.gitignore index 9897bb4..2fd8e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,4 @@ secrets/ # Uploads (reference images, not tracked) uploads/ +logs/ diff --git a/assets/art/librarian_logo.png b/assets/art/librarian_logo.png new file mode 100644 index 0000000..26a15e6 Binary files /dev/null and b/assets/art/librarian_logo.png differ diff --git a/assets/art/logo-tatlock.png b/assets/art/logo-tatlock.png new file mode 100644 index 0000000..db95ec9 Binary files /dev/null and b/assets/art/logo-tatlock.png differ diff --git a/assets/art/the_library_login.png b/assets/art/the_library_login.png new file mode 100644 index 0000000..d821f7c Binary files /dev/null and b/assets/art/the_library_login.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/docs/CORE_API_SPEC.md b/docs/CORE_API_SPEC.md new file mode 100644 index 0000000..8f76dc7 --- /dev/null +++ b/docs/CORE_API_SPEC.md @@ -0,0 +1,346 @@ +# Core API Specification + +Expected API endpoints for the Tatlock UI Control Room feature. + +**Base URL:** `http://{host}:8083` (configurable via `CORE_API_URL`) + +--- + +## Containers + +### List Containers + +``` +GET /infrastructure/containers +``` + +**Query Parameters:** +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `all` | boolean | `true` | Include stopped containers | + +**Response:** `200 OK` +```json +[ + { + "Id": "abc123def456...", + "Names": ["/container-name"], + "Image": "nginx:latest", + "State": "running", + "Status": "Up 2 hours", + "Labels": { + "com.docker.compose.project": "my-stack", + "com.docker.compose.service": "web" + }, + "Ports": [ + { + "IP": "0.0.0.0", + "PrivatePort": 80, + "PublicPort": 8080, + "Type": "tcp" + } + ], + "Mounts": [ + { + "Type": "bind", + "Source": "/host/path", + "Destination": "/container/path", + "Mode": "rw", + "RW": true + } + ], + "NetworkSettings": { + "Networks": { + "bridge": {} + } + }, + "Created": 1704067200, + "SizeRw": 12345, + "SizeRootFs": 67890 + } +] +``` + +--- + +### Get Container + +``` +GET /infrastructure/containers/{id} +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `id` | string | Full container ID | + +**Response:** `200 OK` +```json +{ + "Id": "abc123def456...", + "Names": ["/container-name"], + "Image": "nginx:latest", + "State": "running", + "Status": "Up 2 hours", + "Labels": {}, + "Ports": [], + "Mounts": [], + "NetworkSettings": {}, + "Created": 1704067200 +} +``` + +--- + +### Container Actions + +``` +POST /infrastructure/containers/{id}/{action} +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `id` | string | Full container ID | +| `action` | string | One of: `start`, `stop`, `restart` | + +**Response:** `204 No Content` + +--- + +### Get Container Logs + +``` +GET /infrastructure/containers/{id}/logs +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `id` | string | Full container ID | + +**Query Parameters:** +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `tail` | integer | (all) | Number of lines from end | +| `timestamps` | boolean | `false` | Include timestamps | + +**Response:** `200 OK` +``` +Content-Type: text/plain + +2024-01-01T12:00:00Z Log line 1 +2024-01-01T12:00:01Z Log line 2 +... +``` + +--- + +### Delete Container + +``` +DELETE /infrastructure/containers/{id} +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `id` | string | Full container ID | + +**Query Parameters:** +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `force` | boolean | `false` | Force remove running container | + +**Response:** `204 No Content` + +--- + +## Stacks + +Stacks are Docker Compose projects. The stack ID is the value of the `com.docker.compose.project` label on containers. + +### Get Stack Compose YAML + +``` +GET /infrastructure/stacks/{stackId}/compose +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `stackId` | string | Stack name (compose project name) | + +**Response:** `200 OK` +``` +Content-Type: text/yaml + +version: '3.8' +services: + web: + image: nginx:latest + ports: + - "8080:80" + db: + image: postgres:14 + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} +``` + +--- + +### Update Stack Compose YAML + +``` +PUT /infrastructure/stacks/{stackId}/compose +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `stackId` | string | Stack name (compose project name) | + +**Request Body:** +``` +Content-Type: text/yaml + +version: '3.8' +services: + web: + image: nginx:latest + ... +``` + +**Response:** `204 No Content` + +--- + +### Get Stack Environment Variables + +``` +GET /infrastructure/stacks/{stackId}/env +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `stackId` | string | Stack name (compose project name) | + +**Response:** `200 OK` +```json +{ + "DB_PASSWORD": "secret123", + "API_KEY": "abc-def-ghi", + "DEBUG": "false" +} +``` + +--- + +### Update Stack Environment Variables + +``` +PUT /infrastructure/stacks/{stackId}/env +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `stackId` | string | Stack name (compose project name) | + +**Request Body:** +```json +{ + "DB_PASSWORD": "new-secret", + "API_KEY": "new-key", + "DEBUG": "true" +} +``` + +**Response:** `204 No Content` + +--- + +### Deploy Stack + +Redeploys the stack with current YAML and environment variables. + +``` +POST /infrastructure/stacks/{stackId}/deploy +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `stackId` | string | Stack name (compose project name) | + +**Response:** `202 Accepted` + +--- + +### Rebuild Stack + +Pulls fresh images and recreates all containers in the stack. + +``` +POST /infrastructure/stacks/{stackId}/rebuild +``` + +**Path Parameters:** +| Parameter | Type | Description | +|-----------|------|-------------| +| `stackId` | string | Stack name (compose project name) | + +**Response:** `202 Accepted` + +--- + +## Data Types Reference + +### Container States + +| Value | Description | +|-------|-------------| +| `created` | Container created but not started | +| `running` | Container is running | +| `paused` | Container is paused | +| `restarting` | Container is restarting | +| `removing` | Container is being removed | +| `exited` | Container has exited | +| `dead` | Container is dead | + +### Port Types + +| Value | Description | +|-------|-------------| +| `tcp` | TCP port | +| `udp` | UDP port | + +### Mount Types + +| Value | Description | +|-------|-------------| +| `bind` | Bind mount from host | +| `volume` | Docker volume | +| `tmpfs` | Temporary filesystem | + +--- + +## Error Responses + +All endpoints return standard HTTP error codes: + +| Code | Description | +|------|-------------| +| `400` | Bad Request - Invalid parameters | +| `404` | Not Found - Container/Stack not found | +| `409` | Conflict - Container already in requested state | +| `500` | Internal Server Error | + +**Error Body:** +```json +{ + "error": "Container not found", + "code": "NOT_FOUND" +} +``` diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end