From 62dd0d162bf58efbe609f75c12576be16013472a Mon Sep 17 00:00:00 2001 From: salirezav Date: Tue, 12 Aug 2025 13:48:17 -0400 Subject: [PATCH] Refactor: enhance API response schemas for pagination; update environment variables for Supabase and Vision API; improve Vite configuration for proxy routing --- .../video/presentation/schemas.py | 20 ++++++++++++++----- docker-compose.yml | 16 ++++++++------- management-dashboard-web-app/.env.example | 8 ++++---- .../video-streaming/services/videoApi.ts | 5 ++--- .../src/lib/supabase.ts | 10 +++++++--- .../src/lib/visionApi.ts | 15 +++++++------- .../src/vite-env.d.ts | 1 + .../supabase/.temp/cli-latest | 2 +- management-dashboard-web-app/vite.config.ts | 15 ++++++++++---- 9 files changed, 57 insertions(+), 35 deletions(-) diff --git a/camera-management-api/usda_vision_system/video/presentation/schemas.py b/camera-management-api/usda_vision_system/video/presentation/schemas.py index ee3df10..dc02da4 100644 --- a/camera-management-api/usda_vision_system/video/presentation/schemas.py +++ b/camera-management-api/usda_vision_system/video/presentation/schemas.py @@ -71,13 +71,21 @@ class VideoInfoResponse(BaseModel): class VideoListResponse(BaseModel): """Video list response""" videos: List[VideoInfoResponse] = Field(..., description="List of videos") - total_count: int = Field(..., description="Total number of videos") - + total_count: int = Field(..., description="Total number of matching videos (ignores pagination)") + page: Optional[int] = Field(None, description="Current page number (if using page/limit)") + total_pages: Optional[int] = Field(None, description="Total pages (if using page/limit)") + has_next: Optional[bool] = Field(None, description="Is there a next page?") + has_previous: Optional[bool] = Field(None, description="Is there a previous page?") + class Config: schema_extra = { "example": { "videos": [], - "total_count": 0 + "total_count": 0, + "page": 1, + "total_pages": 1, + "has_next": False, + "has_previous": False } } @@ -108,8 +116,9 @@ class VideoListRequest(BaseModel): start_date: Optional[datetime] = Field(None, description="Filter by start date") end_date: Optional[datetime] = Field(None, description="Filter by end date") limit: Optional[int] = Field(50, description="Maximum number of results") - include_metadata: bool = Field(False, description="Include video metadata") - + offset: Optional[int] = Field(0, description="Number of items to skip (for pagination)") + include_metadata: bool = Field(False, description="Include video metadata for returned items only") + class Config: schema_extra = { "example": { @@ -117,6 +126,7 @@ class VideoListRequest(BaseModel): "start_date": "2025-08-04T00:00:00", "end_date": "2025-08-04T23:59:59", "limit": 50, + "offset": 0, "include_metadata": True } } diff --git a/docker-compose.yml b/docker-compose.yml index df2be03..6cee373 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.9" - services: api: build: @@ -8,11 +6,14 @@ services: working_dir: /app volumes: - ./camera-management-api:/app - - ./camera-management-api/storage:/storage + - /storage:/storage + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro environment: - PYTHONUNBUFFERED=1 - LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib - PYTHONPATH=/app:/app/camera_sdk + - TZ=America/New_York command: > sh -lc " apt-get update && apt-get install -y libusb-1.0-0-dev; @@ -39,8 +40,7 @@ services: # Start the application python main.py --config config.compose.json " - ports: - - "8000:8000" + network_mode: host web: image: node:20-alpine @@ -49,13 +49,15 @@ services: - ./management-dashboard-web-app:/app environment: - CHOKIDAR_USEPOLLING=true - - VITE_SUPABASE_URL=${VITE_SUPABASE_URL} - - VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY} + - TZ=America/New_York command: > sh -lc " npm ci; npm run dev -- --host 0.0.0.0 --port 8080 " + # Ensure the web container can resolve host.docker.internal on Linux + extra_hosts: + - "host.docker.internal:host-gateway" ports: - "8080:8080" diff --git a/management-dashboard-web-app/.env.example b/management-dashboard-web-app/.env.example index aefefe1..b38c14a 100644 --- a/management-dashboard-web-app/.env.example +++ b/management-dashboard-web-app/.env.example @@ -1,10 +1,10 @@ # Environment Configuration for Pecan Experiments Application # USDA Vision Camera System API Configuration -# Default: http://localhost:8000 (current working setup) -# For localhost setup, use: http://localhost:8000 -# For remote systems, use: http://192.168.1.100:8000 (replace with actual IP) -VITE_VISION_API_URL=http://localhost:8000 +# Recommended default: use a relative path so the dev server proxy routes to the API container +# Leave unset to default to "/api" (see vite.config.ts proxy) +# To override and point directly, set e.g.: +# VITE_VISION_API_URL=http://vm-host-or-ip:8000 # Supabase Configuration (if needed for production) # VITE_SUPABASE_URL=your_supabase_url diff --git a/management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts b/management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts index 40bcb7b..2af098b 100644 --- a/management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts +++ b/management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts @@ -15,9 +15,8 @@ import { } from '../types'; import { performanceMonitor } from '../utils/performanceMonitor'; -// Configuration - Use environment variable or default to vision container -// The API is accessible at localhost:8000 in the current setup -const API_BASE_URL = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000'; +// Configuration - Prefer env var; default to relative "/api" so Vite proxy can route to the API container +const API_BASE_URL = import.meta.env.VITE_VISION_API_URL || '/api'; /** * Custom error class for video API errors diff --git a/management-dashboard-web-app/src/lib/supabase.ts b/management-dashboard-web-app/src/lib/supabase.ts index b772f1c..84a7fc0 100644 --- a/management-dashboard-web-app/src/lib/supabase.ts +++ b/management-dashboard-web-app/src/lib/supabase.ts @@ -1,8 +1,12 @@ import { createClient } from '@supabase/supabase-js' -// Local development configuration -const supabaseUrl = 'http://127.0.0.1:54321' -const supabaseAnonKey = '[REDACTED]' +// Supabase configuration from environment (Vite) +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY + +if (!supabaseUrl || !supabaseAnonKey) { + throw new Error('Supabase is not configured. Please set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in your environment (.env)') +} export const supabase = createClient(supabaseUrl, supabaseAnonKey) diff --git a/management-dashboard-web-app/src/lib/visionApi.ts b/management-dashboard-web-app/src/lib/visionApi.ts index 71f12dd..6e496c3 100644 --- a/management-dashboard-web-app/src/lib/visionApi.ts +++ b/management-dashboard-web-app/src/lib/visionApi.ts @@ -1,7 +1,6 @@ // Vision System API Client -// Base URL for the vision system API - Use environment variable or default to vision container -// The API is accessible at localhost:8000 in the current setup -const VISION_API_BASE_URL = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000' +// Base URL for the vision system API - Prefer env var; default to relative "/api" so Vite proxy can route to the API container +const VISION_API_BASE_URL = import.meta.env.VITE_VISION_API_URL || '/api' // Types based on the API documentation export interface SystemStatus { @@ -290,7 +289,7 @@ class VisionApiClient { private async request(endpoint: string, options: RequestInit = {}): Promise { const url = `${this.baseUrl}${endpoint}` - + const response = await fetch(url, { headers: { 'Content-Type': 'application/json', @@ -424,8 +423,8 @@ class VisionApiClient { } catch (error: any) { // If the error is related to missing auto-recording fields, try to handle it gracefully if (error.message?.includes('auto_start_recording_enabled') || - error.message?.includes('auto_recording_max_retries') || - error.message?.includes('auto_recording_retry_delay_seconds')) { + error.message?.includes('auto_recording_max_retries') || + error.message?.includes('auto_recording_retry_delay_seconds')) { // Try to get the raw camera data and add default auto-recording fields try { @@ -540,7 +539,7 @@ export const formatDuration = (seconds: number): string => { const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) const secs = Math.floor(seconds % 60) - + if (hours > 0) { return `${hours}h ${minutes}m ${secs}s` } else if (minutes > 0) { @@ -554,7 +553,7 @@ export const formatUptime = (seconds: number): string => { const days = Math.floor(seconds / 86400) const hours = Math.floor((seconds % 86400) / 3600) const minutes = Math.floor((seconds % 3600) / 60) - + if (days > 0) { return `${days}d ${hours}h ${minutes}m` } else if (hours > 0) { diff --git a/management-dashboard-web-app/src/vite-env.d.ts b/management-dashboard-web-app/src/vite-env.d.ts index 1df6d9e..46479a0 100644 --- a/management-dashboard-web-app/src/vite-env.d.ts +++ b/management-dashboard-web-app/src/vite-env.d.ts @@ -3,6 +3,7 @@ interface ImportMetaEnv { readonly VITE_SUPABASE_URL: string; readonly VITE_SUPABASE_ANON_KEY: string; + readonly VITE_VISION_API_URL?: string; // optional; defaults to "/api" via vite proxy } interface ImportMeta { diff --git a/management-dashboard-web-app/supabase/.temp/cli-latest b/management-dashboard-web-app/supabase/.temp/cli-latest index a73e44b..8e00c6d 100644 --- a/management-dashboard-web-app/supabase/.temp/cli-latest +++ b/management-dashboard-web-app/supabase/.temp/cli-latest @@ -1 +1 @@ -v2.31.8 \ No newline at end of file +v2.33.9 \ No newline at end of file diff --git a/management-dashboard-web-app/vite.config.ts b/management-dashboard-web-app/vite.config.ts index ed12bb0..c8c46f5 100644 --- a/management-dashboard-web-app/vite.config.ts +++ b/management-dashboard-web-app/vite.config.ts @@ -9,9 +9,16 @@ export default defineConfig({ tailwindcss(), ], server: { - // Allow connecting via this VM's hostname - allowedHosts: ['exp-dash'], - // host is provided via CLI in docker-compose, but keeping this commented for local use: - // host: true, + // Allow connections from the VM hostname and any other host/IP + allowedHosts: ['exp-dash', 'localhost'], + // Proxy API calls from the browser to the API container via the compose service name + proxy: { + '/api': { + // Route to API via the host so this works whether API is on bridge (via port mapping) or host network + target: 'http://host.docker.internal:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, }, })