Refactor: enhance API response schemas for pagination; update environment variables for Supabase and Vision API; improve Vite configuration for proxy routing
This commit is contained in:
@@ -71,13 +71,21 @@ class VideoInfoResponse(BaseModel):
|
|||||||
class VideoListResponse(BaseModel):
|
class VideoListResponse(BaseModel):
|
||||||
"""Video list response"""
|
"""Video list response"""
|
||||||
videos: List[VideoInfoResponse] = Field(..., description="List of videos")
|
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:
|
class Config:
|
||||||
schema_extra = {
|
schema_extra = {
|
||||||
"example": {
|
"example": {
|
||||||
"videos": [],
|
"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")
|
start_date: Optional[datetime] = Field(None, description="Filter by start date")
|
||||||
end_date: Optional[datetime] = Field(None, description="Filter by end date")
|
end_date: Optional[datetime] = Field(None, description="Filter by end date")
|
||||||
limit: Optional[int] = Field(50, description="Maximum number of results")
|
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:
|
class Config:
|
||||||
schema_extra = {
|
schema_extra = {
|
||||||
"example": {
|
"example": {
|
||||||
@@ -117,6 +126,7 @@ class VideoListRequest(BaseModel):
|
|||||||
"start_date": "2025-08-04T00:00:00",
|
"start_date": "2025-08-04T00:00:00",
|
||||||
"end_date": "2025-08-04T23:59:59",
|
"end_date": "2025-08-04T23:59:59",
|
||||||
"limit": 50,
|
"limit": 50,
|
||||||
|
"offset": 0,
|
||||||
"include_metadata": True
|
"include_metadata": True
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: "3.9"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
api:
|
api:
|
||||||
build:
|
build:
|
||||||
@@ -8,11 +6,14 @@ services:
|
|||||||
working_dir: /app
|
working_dir: /app
|
||||||
volumes:
|
volumes:
|
||||||
- ./camera-management-api:/app
|
- ./camera-management-api:/app
|
||||||
- ./camera-management-api/storage:/storage
|
- /storage:/storage
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib
|
- LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib
|
||||||
- PYTHONPATH=/app:/app/camera_sdk
|
- PYTHONPATH=/app:/app/camera_sdk
|
||||||
|
- TZ=America/New_York
|
||||||
command: >
|
command: >
|
||||||
sh -lc "
|
sh -lc "
|
||||||
apt-get update && apt-get install -y libusb-1.0-0-dev;
|
apt-get update && apt-get install -y libusb-1.0-0-dev;
|
||||||
@@ -39,8 +40,7 @@ services:
|
|||||||
# Start the application
|
# Start the application
|
||||||
python main.py --config config.compose.json
|
python main.py --config config.compose.json
|
||||||
"
|
"
|
||||||
ports:
|
network_mode: host
|
||||||
- "8000:8000"
|
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: node:20-alpine
|
image: node:20-alpine
|
||||||
@@ -49,13 +49,15 @@ services:
|
|||||||
- ./management-dashboard-web-app:/app
|
- ./management-dashboard-web-app:/app
|
||||||
environment:
|
environment:
|
||||||
- CHOKIDAR_USEPOLLING=true
|
- CHOKIDAR_USEPOLLING=true
|
||||||
- VITE_SUPABASE_URL=${VITE_SUPABASE_URL}
|
- TZ=America/New_York
|
||||||
- VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY}
|
|
||||||
command: >
|
command: >
|
||||||
sh -lc "
|
sh -lc "
|
||||||
npm ci;
|
npm ci;
|
||||||
npm run dev -- --host 0.0.0.0 --port 8080
|
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:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Environment Configuration for Pecan Experiments Application
|
# Environment Configuration for Pecan Experiments Application
|
||||||
|
|
||||||
# USDA Vision Camera System API Configuration
|
# USDA Vision Camera System API Configuration
|
||||||
# Default: http://localhost:8000 (current working setup)
|
# Recommended default: use a relative path so the dev server proxy routes to the API container
|
||||||
# For localhost setup, use: http://localhost:8000
|
# Leave unset to default to "/api" (see vite.config.ts proxy)
|
||||||
# For remote systems, use: http://192.168.1.100:8000 (replace with actual IP)
|
# To override and point directly, set e.g.:
|
||||||
VITE_VISION_API_URL=http://localhost:8000
|
# VITE_VISION_API_URL=http://vm-host-or-ip:8000
|
||||||
|
|
||||||
# Supabase Configuration (if needed for production)
|
# Supabase Configuration (if needed for production)
|
||||||
# VITE_SUPABASE_URL=your_supabase_url
|
# VITE_SUPABASE_URL=your_supabase_url
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import { performanceMonitor } from '../utils/performanceMonitor';
|
import { performanceMonitor } from '../utils/performanceMonitor';
|
||||||
|
|
||||||
// Configuration - Use environment variable or default to vision container
|
// Configuration - Prefer env var; default to relative "/api" so Vite proxy can route to the API container
|
||||||
// The API is accessible at localhost:8000 in the current setup
|
const API_BASE_URL = import.meta.env.VITE_VISION_API_URL || '/api';
|
||||||
const API_BASE_URL = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom error class for video API errors
|
* Custom error class for video API errors
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { createClient } from '@supabase/supabase-js'
|
import { createClient } from '@supabase/supabase-js'
|
||||||
|
|
||||||
// Local development configuration
|
// Supabase configuration from environment (Vite)
|
||||||
const supabaseUrl = 'http://127.0.0.1:54321'
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||||
const supabaseAnonKey = '[REDACTED]'
|
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)
|
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Vision System API Client
|
// Vision System API Client
|
||||||
// Base URL for the vision system API - Use environment variable or default to vision container
|
// Base URL for the vision system API - Prefer env var; default to relative "/api" so Vite proxy can route to the API container
|
||||||
// The API is accessible at localhost:8000 in the current setup
|
const VISION_API_BASE_URL = import.meta.env.VITE_VISION_API_URL || '/api'
|
||||||
const VISION_API_BASE_URL = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000'
|
|
||||||
|
|
||||||
// Types based on the API documentation
|
// Types based on the API documentation
|
||||||
export interface SystemStatus {
|
export interface SystemStatus {
|
||||||
@@ -290,7 +289,7 @@ class VisionApiClient {
|
|||||||
|
|
||||||
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||||
const url = `${this.baseUrl}${endpoint}`
|
const url = `${this.baseUrl}${endpoint}`
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -424,8 +423,8 @@ class VisionApiClient {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// If the error is related to missing auto-recording fields, try to handle it gracefully
|
// If the error is related to missing auto-recording fields, try to handle it gracefully
|
||||||
if (error.message?.includes('auto_start_recording_enabled') ||
|
if (error.message?.includes('auto_start_recording_enabled') ||
|
||||||
error.message?.includes('auto_recording_max_retries') ||
|
error.message?.includes('auto_recording_max_retries') ||
|
||||||
error.message?.includes('auto_recording_retry_delay_seconds')) {
|
error.message?.includes('auto_recording_retry_delay_seconds')) {
|
||||||
|
|
||||||
// Try to get the raw camera data and add default auto-recording fields
|
// Try to get the raw camera data and add default auto-recording fields
|
||||||
try {
|
try {
|
||||||
@@ -540,7 +539,7 @@ export const formatDuration = (seconds: number): string => {
|
|||||||
const hours = Math.floor(seconds / 3600)
|
const hours = Math.floor(seconds / 3600)
|
||||||
const minutes = Math.floor((seconds % 3600) / 60)
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
const secs = Math.floor(seconds % 60)
|
const secs = Math.floor(seconds % 60)
|
||||||
|
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
return `${hours}h ${minutes}m ${secs}s`
|
return `${hours}h ${minutes}m ${secs}s`
|
||||||
} else if (minutes > 0) {
|
} else if (minutes > 0) {
|
||||||
@@ -554,7 +553,7 @@ export const formatUptime = (seconds: number): string => {
|
|||||||
const days = Math.floor(seconds / 86400)
|
const days = Math.floor(seconds / 86400)
|
||||||
const hours = Math.floor((seconds % 86400) / 3600)
|
const hours = Math.floor((seconds % 86400) / 3600)
|
||||||
const minutes = Math.floor((seconds % 3600) / 60)
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
|
||||||
if (days > 0) {
|
if (days > 0) {
|
||||||
return `${days}d ${hours}h ${minutes}m`
|
return `${days}d ${hours}h ${minutes}m`
|
||||||
} else if (hours > 0) {
|
} else if (hours > 0) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_SUPABASE_URL: string;
|
readonly VITE_SUPABASE_URL: string;
|
||||||
readonly VITE_SUPABASE_ANON_KEY: string;
|
readonly VITE_SUPABASE_ANON_KEY: string;
|
||||||
|
readonly VITE_VISION_API_URL?: string; // optional; defaults to "/api" via vite proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v2.31.8
|
v2.33.9
|
||||||
@@ -9,9 +9,16 @@ export default defineConfig({
|
|||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
// Allow connecting via this VM's hostname
|
// Allow connections from the VM hostname and any other host/IP
|
||||||
allowedHosts: ['exp-dash'],
|
allowedHosts: ['exp-dash', 'localhost'],
|
||||||
// host is provided via CLI in docker-compose, but keeping this commented for local use:
|
// Proxy API calls from the browser to the API container via the compose service name
|
||||||
// host: true,
|
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/, ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user