WIP: integrate-old-refactors-of-github #1
@@ -688,6 +688,29 @@ class APIServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Failed to add video routes: {e}")
|
self.logger.error(f"Failed to add video routes: {e}")
|
||||||
|
|
||||||
|
@self.app.get("/debug/camera-manager")
|
||||||
|
async def debug_camera_manager():
|
||||||
|
"""Debug endpoint to check camera manager state"""
|
||||||
|
try:
|
||||||
|
if not self.camera_manager:
|
||||||
|
return {"error": "Camera manager not available"}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"available_cameras": len(self.camera_manager.available_cameras),
|
||||||
|
"camera_recorders": list(self.camera_manager.camera_recorders.keys()),
|
||||||
|
"camera_streamers": list(self.camera_manager.camera_streamers.keys()),
|
||||||
|
"streamer_states": {
|
||||||
|
name: {
|
||||||
|
"exists": streamer is not None,
|
||||||
|
"is_streaming": streamer.is_streaming() if streamer else False,
|
||||||
|
"streaming": getattr(streamer, 'streaming', False) if streamer else False
|
||||||
|
}
|
||||||
|
for name, streamer in self.camera_manager.camera_streamers.items()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
def _setup_event_subscriptions(self):
|
def _setup_event_subscriptions(self):
|
||||||
"""Setup event subscriptions for WebSocket broadcasting"""
|
"""Setup event subscriptions for WebSocket broadcasting"""
|
||||||
|
|
||||||
|
|||||||
@@ -460,12 +460,16 @@ class CameraManager:
|
|||||||
|
|
||||||
def _initialize_streamers(self) -> None:
|
def _initialize_streamers(self) -> None:
|
||||||
"""Initialize camera streamers for configured cameras"""
|
"""Initialize camera streamers for configured cameras"""
|
||||||
|
self.logger.info("Starting camera streamer initialization...")
|
||||||
with self._lock:
|
with self._lock:
|
||||||
for camera_config in self.config.cameras:
|
for camera_config in self.config.cameras:
|
||||||
if not camera_config.enabled:
|
if not camera_config.enabled:
|
||||||
|
self.logger.debug(f"Skipping disabled camera: {camera_config.name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self.logger.info(f"Initializing streamer for camera: {camera_config.name}")
|
||||||
|
|
||||||
# Find matching physical camera
|
# Find matching physical camera
|
||||||
device_info = self._find_camera_device(camera_config.name)
|
device_info = self._find_camera_device(camera_config.name)
|
||||||
if device_info is None:
|
if device_info is None:
|
||||||
@@ -481,6 +485,10 @@ class CameraManager:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error initializing streamer for {camera_config.name}: {e}")
|
self.logger.error(f"Error initializing streamer for {camera_config.name}: {e}")
|
||||||
|
import traceback
|
||||||
|
self.logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
|
|
||||||
|
self.logger.info(f"Camera streamer initialization complete. Created {len(self.camera_streamers)} streamers: {list(self.camera_streamers.keys())}")
|
||||||
|
|
||||||
def get_camera_streamer(self, camera_name: str) -> Optional[CameraStreamer]:
|
def get_camera_streamer(self, camera_name: str) -> Optional[CameraStreamer]:
|
||||||
"""Get camera streamer for a specific camera"""
|
"""Get camera streamer for a specific camera"""
|
||||||
|
|||||||
@@ -172,14 +172,37 @@ class CameraMonitor:
|
|||||||
if not device_info:
|
if not device_info:
|
||||||
return "disconnected", "Camera device not found", None
|
return "disconnected", "Camera device not found", None
|
||||||
|
|
||||||
|
# ALWAYS check our streamer state first, before doing any camera availability tests
|
||||||
|
streamer = self.camera_manager.camera_streamers.get(camera_name)
|
||||||
|
self.logger.info(f"Checking streamer for {camera_name}: {streamer}")
|
||||||
|
if streamer and streamer.is_streaming():
|
||||||
|
self.logger.info(f"Camera {camera_name} is streaming - setting status to streaming")
|
||||||
|
return "streaming", "Camera streaming (live preview)", self._get_device_info_dict(device_info)
|
||||||
|
|
||||||
|
# Also check if our recorder is active
|
||||||
|
recorder = self.camera_manager.camera_recorders.get(camera_name)
|
||||||
|
if recorder and recorder.hCamera and recorder.recording:
|
||||||
|
self.logger.info(f"Camera {camera_name} is recording - setting status to available")
|
||||||
|
return "available", "Camera recording (in use by system)", self._get_device_info_dict(device_info)
|
||||||
|
|
||||||
# Check if camera is already opened by another process
|
# Check if camera is already opened by another process
|
||||||
if mvsdk.CameraIsOpened(device_info):
|
try:
|
||||||
# Camera is opened - check if it's our recorder that's currently recording
|
self.logger.info(f"Checking if camera {camera_name} is opened...")
|
||||||
recorder = self.camera_manager.camera_recorders.get(camera_name)
|
is_opened = mvsdk.CameraIsOpened(device_info)
|
||||||
if recorder and recorder.hCamera and recorder.recording:
|
self.logger.info(f"CameraIsOpened result for {camera_name}: {is_opened}")
|
||||||
return "available", "Camera recording (in use by system)", self._get_device_info_dict(device_info)
|
|
||||||
else:
|
if is_opened:
|
||||||
|
self.logger.info(f"Camera {camera_name} is opened by another process - setting status to busy")
|
||||||
return "busy", "Camera opened by another process", self._get_device_info_dict(device_info)
|
return "busy", "Camera opened by another process", self._get_device_info_dict(device_info)
|
||||||
|
else:
|
||||||
|
self.logger.info(f"Camera {camera_name} is not opened, will try initialization")
|
||||||
|
# Camera is not opened, so we can try to initialize it
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"CameraIsOpened failed for {camera_name}: {e}")
|
||||||
|
# If we can't determine the status, try to initialize to see what happens
|
||||||
|
self.logger.info(f"CameraIsOpened failed for {camera_name}, will try initialization: {e}")
|
||||||
|
|
||||||
# Try to initialize camera briefly to test availability
|
# Try to initialize camera briefly to test availability
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class CameraStatus(Enum):
|
|||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
AVAILABLE = "available"
|
AVAILABLE = "available"
|
||||||
BUSY = "busy"
|
BUSY = "busy"
|
||||||
|
STREAMING = "streaming" # New status for when camera is streaming
|
||||||
ERROR = "error"
|
ERROR = "error"
|
||||||
DISCONNECTED = "disconnected"
|
DISCONNECTED = "disconnected"
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ services:
|
|||||||
web:
|
web:
|
||||||
image: node:20-alpine
|
image: node:20-alpine
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
|
env_file:
|
||||||
|
- ./management-dashboard-web-app/.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./management-dashboard-web-app:/app
|
- ./management-dashboard-web-app:/app
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
0
management-dashboard-web-app/.env.example
Normal file → Executable file
0
management-dashboard-web-app/.env.example
Normal file → Executable file
0
management-dashboard-web-app/.gitignore
vendored
Normal file → Executable file
0
management-dashboard-web-app/.gitignore
vendored
Normal file → Executable file
0
management-dashboard-web-app/.vscode/extensions.json
vendored
Normal file → Executable file
0
management-dashboard-web-app/.vscode/extensions.json
vendored
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/AI_AGENT_VIDEO_INTEGRATION_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/AI_AGENT_VIDEO_INTEGRATION_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/API_CHANGES_SUMMARY.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/API_CHANGES_SUMMARY.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/API_DOCUMENTATION.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/API_DOCUMENTATION.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/API_QUICK_REFERENCE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/API_QUICK_REFERENCE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/CURRENT_CONFIGURATION.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/CURRENT_CONFIGURATION.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/MP4_FORMAT_UPDATE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/MP4_FORMAT_UPDATE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/PROJECT_COMPLETE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/PROJECT_COMPLETE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/REACT_INTEGRATION_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/REACT_INTEGRATION_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/VIDEO_STREAMING.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/VIDEO_STREAMING.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/WEB_AI_AGENT_VIDEO_INTEGRATION.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/WEB_AI_AGENT_VIDEO_INTEGRATION.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/api/CAMERA_CONFIG_API.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/api/CAMERA_CONFIG_API.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/camera/BLOWER_CAMERA_CONFIG.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/camera/BLOWER_CAMERA_CONFIG.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/camera/CONVEYOR_CAMERA_CONFIG.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/camera/CONVEYOR_CAMERA_CONFIG.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/camera/PREVIEW_ENHANCEMENT.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/camera/PREVIEW_ENHANCEMENT.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/features/AUTO_RECORDING_FEATURE_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/features/AUTO_RECORDING_FEATURE_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/guides/CAMERA_RECOVERY_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/guides/CAMERA_RECOVERY_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/guides/MQTT_LOGGING_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/guides/MQTT_LOGGING_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/guides/STREAMING_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/guides/STREAMING_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/01README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/01README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/IMPLEMENTATION_SUMMARY.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/IMPLEMENTATION_SUMMARY.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/README_SYSTEM.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/README_SYSTEM.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/TIMEZONE_SETUP_SUMMARY.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/TIMEZONE_SETUP_SUMMARY.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/VIDEO_RECORDER_README.md
Normal file → Executable file
0
management-dashboard-web-app/API Documentations/docs/legacy/VIDEO_RECORDER_README.md
Normal file → Executable file
210
management-dashboard-web-app/CAMERA_ROUTE_IMPLEMENTATION.md
Executable file
210
management-dashboard-web-app/CAMERA_ROUTE_IMPLEMENTATION.md
Executable file
@@ -0,0 +1,210 @@
|
|||||||
|
# 🎥 Camera Route Implementation Guide
|
||||||
|
|
||||||
|
This document explains the implementation of the new public camera live view routes (`/camera#/live`) that don't require authentication.
|
||||||
|
|
||||||
|
## 🚀 What Was Implemented
|
||||||
|
|
||||||
|
### 1. **LiveCameraView Component** (`src/components/LiveCameraView.tsx`)
|
||||||
|
- Displays live camera feed without authentication requirements
|
||||||
|
- Handles streaming start/stop automatically
|
||||||
|
- Provides error handling and loading states
|
||||||
|
- Full-screen live view with camera label and status indicator
|
||||||
|
|
||||||
|
### 2. **CameraRoute Component** (`src/components/CameraRoute.tsx`)
|
||||||
|
- Validates camera route parameters
|
||||||
|
- Ensures only valid camera numbers (camera1, camera2, etc.) are accepted
|
||||||
|
- Renders the LiveCameraView for valid routes
|
||||||
|
|
||||||
|
### 3. **Updated App.tsx**
|
||||||
|
- Added route pattern matching for `/camera#/live`
|
||||||
|
- Integrated camera routes into existing authentication flow
|
||||||
|
- Maintains backward compatibility with existing functionality
|
||||||
|
|
||||||
|
### 4. **Test Page** (`public/camera-test.html`)
|
||||||
|
- Simple HTML page to test camera routes
|
||||||
|
- Provides links to test different camera numbers
|
||||||
|
- Explains expected behavior
|
||||||
|
|
||||||
|
## 📋 Required Dependencies
|
||||||
|
|
||||||
|
The following packages need to be installed to complete the implementation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install React Router
|
||||||
|
npm install react-router-dom
|
||||||
|
|
||||||
|
# Install TypeScript types
|
||||||
|
npm install --save-dev @types/react-router-dom
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Due to permission issues, these packages couldn't be installed automatically. You'll need to resolve the permissions or install them manually.
|
||||||
|
|
||||||
|
## 🔧 How to Complete the Setup
|
||||||
|
|
||||||
|
### Option 1: Fix Permissions and Install
|
||||||
|
```bash
|
||||||
|
# Fix node_modules permissions
|
||||||
|
sudo chown -R $USER:$USER node_modules
|
||||||
|
sudo chmod -R 755 node_modules
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Manual Installation
|
||||||
|
```bash
|
||||||
|
# Remove problematic node_modules
|
||||||
|
rm -rf node_modules
|
||||||
|
|
||||||
|
# Reinstall everything
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Use Yarn Instead
|
||||||
|
```bash
|
||||||
|
# Install yarn if not available
|
||||||
|
npm install -g yarn
|
||||||
|
|
||||||
|
# Install dependencies with yarn
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Testing the Implementation
|
||||||
|
|
||||||
|
### 1. **Start the Development Server**
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Test Camera Routes**
|
||||||
|
Open these URLs in your browser:
|
||||||
|
- `http://localhost:5173/camera1/live` - Live view of camera1
|
||||||
|
- `http://localhost:5173/camera2/live` - Live view of camera2
|
||||||
|
- `http://localhost:5173/camera3/live` - Live view of camera3
|
||||||
|
|
||||||
|
### 3. **Use the Test Page**
|
||||||
|
Open `http://localhost:5173/camera-test.html` to access the test interface.
|
||||||
|
|
||||||
|
### 4. **Expected Behavior**
|
||||||
|
- ✅ **Valid routes** should show live camera feed
|
||||||
|
- ❌ **Invalid routes** should show error message
|
||||||
|
- 🔒 **Protected routes** should redirect to login
|
||||||
|
|
||||||
|
## 🏗️ Architecture Details
|
||||||
|
|
||||||
|
### Route Pattern
|
||||||
|
```
|
||||||
|
/camera{number}/live
|
||||||
|
```
|
||||||
|
- `{number}` must be a positive integer
|
||||||
|
- Examples: `/camera1/live`, `/camera2/live`, `/camera10/live`
|
||||||
|
- Invalid: `/camera/live`, `/camera0/live`, `/camera-1/live`
|
||||||
|
|
||||||
|
### Component Flow
|
||||||
|
```
|
||||||
|
App.tsx → Route Detection → CameraRoute → LiveCameraView
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Integration
|
||||||
|
The LiveCameraView component integrates with existing camera API endpoints:
|
||||||
|
- `POST /cameras/{camera_name}/start-stream` - Start streaming
|
||||||
|
- `GET /cameras/{camera_name}/stream` - Get MJPEG stream
|
||||||
|
- `POST /cameras/{camera_name}/stop-stream` - Stop streaming
|
||||||
|
|
||||||
|
## 🎯 Key Features
|
||||||
|
|
||||||
|
### ✅ **Public Access**
|
||||||
|
- No authentication required
|
||||||
|
- Anyone can view live camera feeds
|
||||||
|
- Perfect for monitoring displays
|
||||||
|
|
||||||
|
### ✅ **Non-Blocking Streaming**
|
||||||
|
- Uses existing CameraStreamer infrastructure
|
||||||
|
- Separate camera connections for streaming vs. recording
|
||||||
|
- Doesn't interfere with recording operations
|
||||||
|
|
||||||
|
### ✅ **Real-time Video**
|
||||||
|
- MJPEG format for low latency
|
||||||
|
- Automatic stream management
|
||||||
|
- Error handling and retry functionality
|
||||||
|
|
||||||
|
### ✅ **Responsive Design**
|
||||||
|
- Full-screen live view
|
||||||
|
- Camera identification labels
|
||||||
|
- Live status indicators
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### 1. **Permission Errors During Installation**
|
||||||
|
```bash
|
||||||
|
# Fix ownership
|
||||||
|
sudo chown -R $USER:$USER .
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
sudo chmod -R 755 .
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. **Camera Stream Not Loading**
|
||||||
|
- Check if camera API is running (`http://localhost:8000`)
|
||||||
|
- Verify camera configuration in `config.compose.json`
|
||||||
|
- Check browser console for errors
|
||||||
|
|
||||||
|
#### 3. **Route Not Working**
|
||||||
|
- Ensure React app is running
|
||||||
|
- Check browser console for routing errors
|
||||||
|
- Verify component imports are correct
|
||||||
|
|
||||||
|
#### 4. **TypeScript Errors**
|
||||||
|
- Install missing type definitions
|
||||||
|
- Check import paths
|
||||||
|
- Verify component interfaces
|
||||||
|
|
||||||
|
### Debug Steps
|
||||||
|
1. Check browser console for errors
|
||||||
|
2. Verify API endpoints are accessible
|
||||||
|
3. Test camera streaming directly via API
|
||||||
|
4. Check component rendering in React DevTools
|
||||||
|
|
||||||
|
## 🚀 Next Steps
|
||||||
|
|
||||||
|
### Immediate
|
||||||
|
1. Install required dependencies
|
||||||
|
2. Test basic functionality
|
||||||
|
3. Verify camera streaming works
|
||||||
|
|
||||||
|
### Future Enhancements
|
||||||
|
1. **Add React Router** for better routing
|
||||||
|
2. **Implement URL-based navigation** between cameras
|
||||||
|
3. **Add camera selection interface**
|
||||||
|
4. **Implement stream quality controls**
|
||||||
|
5. **Add recording controls** (if needed)
|
||||||
|
|
||||||
|
### Production Considerations
|
||||||
|
1. **Security**: Consider adding rate limiting
|
||||||
|
2. **Performance**: Optimize for multiple concurrent viewers
|
||||||
|
3. **Monitoring**: Add analytics and usage tracking
|
||||||
|
4. **Access Control**: Implement optional authentication if needed
|
||||||
|
|
||||||
|
## 📚 Related Documentation
|
||||||
|
|
||||||
|
- [Camera API Documentation](../camera-management-api/docs/API_DOCUMENTATION.md)
|
||||||
|
- [Streaming Guide](../camera-management-api/docs/guides/STREAMING_GUIDE.md)
|
||||||
|
- [Vision System README](VISION_SYSTEM_README.md)
|
||||||
|
|
||||||
|
## 🤝 Support
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
1. Check the troubleshooting section above
|
||||||
|
2. Review browser console for error messages
|
||||||
|
3. Verify camera API is running and accessible
|
||||||
|
4. Test API endpoints directly with curl or Postman
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Implementation Status**: ✅ Components Created | ⚠️ Dependencies Pending | <20><> Ready for Testing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
management-dashboard-web-app/README.md
Normal file → Executable file
0
management-dashboard-web-app/README.md
Normal file → Executable file
0
management-dashboard-web-app/VISION_SYSTEM_README.md
Normal file → Executable file
0
management-dashboard-web-app/VISION_SYSTEM_README.md
Normal file → Executable file
0
management-dashboard-web-app/api-endpoints.http
Normal file → Executable file
0
management-dashboard-web-app/api-endpoints.http
Normal file → Executable file
0
management-dashboard-web-app/docs/AUTO_RECORDING_SETUP.md
Normal file → Executable file
0
management-dashboard-web-app/docs/AUTO_RECORDING_SETUP.md
Normal file → Executable file
0
management-dashboard-web-app/docs/MODULAR_ARCHITECTURE_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/docs/MODULAR_ARCHITECTURE_GUIDE.md
Normal file → Executable file
0
management-dashboard-web-app/docs/MP4_FRONTEND_IMPLEMENTATION_STATUS.md
Normal file → Executable file
0
management-dashboard-web-app/docs/MP4_FRONTEND_IMPLEMENTATION_STATUS.md
Normal file → Executable file
0
management-dashboard-web-app/docs/VIDEO_STREAMING_INTEGRATION.md
Normal file → Executable file
0
management-dashboard-web-app/docs/VIDEO_STREAMING_INTEGRATION.md
Normal file → Executable file
0
management-dashboard-web-app/docs/VIDEO_STREAMING_INTEGRATION_COMPLETE.md
Normal file → Executable file
0
management-dashboard-web-app/docs/VIDEO_STREAMING_INTEGRATION_COMPLETE.md
Normal file → Executable file
0
management-dashboard-web-app/eslint.config.js
Normal file → Executable file
0
management-dashboard-web-app/eslint.config.js
Normal file → Executable file
0
management-dashboard-web-app/index.html
Normal file → Executable file
0
management-dashboard-web-app/index.html
Normal file → Executable file
75
management-dashboard-web-app/package-lock.json
generated
Normal file → Executable file
75
management-dashboard-web-app/package-lock.json
generated
Normal file → Executable file
@@ -13,19 +13,21 @@
|
|||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router-dom": "^6.28.0",
|
||||||
"tailwindcss": "^4.1.11"
|
"tailwindcss": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"eslint": "^9.30.1",
|
"eslint": "^9.30.1",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.35.1",
|
"typescript-eslint": "^8.28.1",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1054,6 +1056,15 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@remix-run/router": {
|
||||||
|
"version": "1.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||||
|
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.19",
|
"version": "1.0.0-beta.19",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
|
||||||
@@ -1709,6 +1720,13 @@
|
|||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/history": {
|
||||||
|
"version": "4.7.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||||
|
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1751,6 +1769,29 @@
|
|||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-router": {
|
||||||
|
"version": "5.1.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
|
||||||
|
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/history": "^4.7.11",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-router-dom": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/history": "^4.7.11",
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-router": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.18.1",
|
"version": "8.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
@@ -3562,6 +3603,38 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
||||||
|
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.23.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
|
||||||
|
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.23.0",
|
||||||
|
"react-router": "6.30.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
|
|||||||
6
management-dashboard-web-app/package.json
Normal file → Executable file
6
management-dashboard-web-app/package.json
Normal file → Executable file
@@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15,19 +15,21 @@
|
|||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router-dom": "^6.28.0",
|
||||||
"tailwindcss": "^4.1.11"
|
"tailwindcss": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"eslint": "^9.30.1",
|
"eslint": "^9.30.1",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.35.1",
|
"typescript-eslint": "^8.28.1",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0
management-dashboard-web-app/phase_2_experimental_run_sheet.csv
Normal file → Executable file
0
management-dashboard-web-app/phase_2_experimental_run_sheet.csv
Normal file → Executable file
119
management-dashboard-web-app/public/camera-test.html
Executable file
119
management-dashboard-web-app/public/camera-test.html
Executable file
@@ -0,0 +1,119 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Camera Route Test</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-links {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-links h2 {
|
||||||
|
color: #333;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-links a {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 10px 10px 10px 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-links a:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
background: #e7f3ff;
|
||||||
|
border: 1px solid #b3d9ff;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>🎥 Camera Route Test</h1>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<h3>Test the New Camera Routes</h3>
|
||||||
|
<p>This page helps you test the new camera live view routes that don't require authentication.</p>
|
||||||
|
<p><strong>Note:</strong> Make sure the React app is running and the camera API is accessible.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-links">
|
||||||
|
<h2>Test Camera Routes</h2>
|
||||||
|
<p>Click the links below to test different camera routes:</p>
|
||||||
|
|
||||||
|
<a href="/camera1/live" target="_blank">Camera 1 Live View</a>
|
||||||
|
<a href="/camera2/live" target="_blank">Camera 2 Live View</a>
|
||||||
|
<a href="/camera3/live" target="_blank">Camera 3 Live View</a>
|
||||||
|
<a href="/camera10/live" target="_blank">Camera 10 Live View</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
<h3>Expected Behavior</h3>
|
||||||
|
<ul>
|
||||||
|
<li>✅ <strong>Valid routes</strong> (like /camera1/live) should show the live camera feed</li>
|
||||||
|
<li>❌ <strong>Invalid routes</strong> (like /camera/live) should show an error message</li>
|
||||||
|
<li>🔒 <strong>Protected routes</strong> (like /) should redirect to login if not authenticated</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<h3>API Endpoints</h3>
|
||||||
|
<p>The camera routes use these backend API endpoints:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>POST /cameras/{camera_name}/start-stream</code> - Start streaming</li>
|
||||||
|
<li><code>GET /cameras/{camera_name}/stream</code> - Get MJPEG stream</li>
|
||||||
|
<li><code>POST /cameras/{camera_name}/stop-stream</code> - Stop streaming</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Add click tracking for analytics
|
||||||
|
document.querySelectorAll('.test-links a').forEach(link => {
|
||||||
|
link.addEventListener('click', function () {
|
||||||
|
console.log('Testing camera route:', this.href);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
management-dashboard-web-app/public/vite.svg
Normal file → Executable file
0
management-dashboard-web-app/public/vite.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
management-dashboard-web-app/src/App.css
Normal file → Executable file
0
management-dashboard-web-app/src/App.css
Normal file → Executable file
21
management-dashboard-web-app/src/App.tsx
Normal file → Executable file
21
management-dashboard-web-app/src/App.tsx
Normal file → Executable file
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
|
|||||||
import { supabase } from './lib/supabase'
|
import { supabase } from './lib/supabase'
|
||||||
import { Login } from './components/Login'
|
import { Login } from './components/Login'
|
||||||
import { Dashboard } from './components/Dashboard'
|
import { Dashboard } from './components/Dashboard'
|
||||||
|
import { CameraRoute } from './components/CameraRoute'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null)
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null)
|
||||||
@@ -84,6 +85,18 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if current route is a camera live route
|
||||||
|
const isCameraLiveRoute = (route: string) => {
|
||||||
|
const cameraRoutePattern = /^\/camera(\d+)\/live$/
|
||||||
|
return cameraRoutePattern.test(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract camera number from route
|
||||||
|
const getCameraNumber = (route: string) => {
|
||||||
|
const match = route.match(/^\/camera(\d+)\/live$/)
|
||||||
|
return match ? `camera${match[1]}` : null
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
@@ -107,6 +120,14 @@ function App() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle camera live routes (no authentication required)
|
||||||
|
if (isCameraLiveRoute(currentRoute)) {
|
||||||
|
const cameraNumber = getCameraNumber(currentRoute)
|
||||||
|
if (cameraNumber) {
|
||||||
|
return <CameraRoute cameraNumber={cameraNumber} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isAuthenticated ? (
|
{isAuthenticated ? (
|
||||||
|
|||||||
0
management-dashboard-web-app/src/assets/react.svg
Normal file → Executable file
0
management-dashboard-web-app/src/assets/react.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
0
management-dashboard-web-app/src/components/AutoRecordingStatus.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/AutoRecordingStatus.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/AutoRecordingTest.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/AutoRecordingTest.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraConfigModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraConfigModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraPreviewModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraPreviewModal.tsx
Normal file → Executable file
25
management-dashboard-web-app/src/components/CameraRoute.tsx
Executable file
25
management-dashboard-web-app/src/components/CameraRoute.tsx
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
import { LiveCameraView } from './LiveCameraView'
|
||||||
|
|
||||||
|
interface CameraRouteProps {
|
||||||
|
cameraNumber: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CameraRoute({ cameraNumber }: CameraRouteProps) {
|
||||||
|
// Validate camera number (only allow camera1, camera2, etc.)
|
||||||
|
if (!cameraNumber || !/^camera\d+$/.test(cameraNumber)) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||||
|
<div className="text-center text-white">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">Invalid Camera</h1>
|
||||||
|
<p className="text-gray-300">Camera number must be in format: camera1, camera2, etc.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LiveCameraView cameraName={cameraNumber} />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
management-dashboard-web-app/src/components/CreateUserModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CreateUserModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Dashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Dashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardHome.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardHome.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardLayout.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardLayout.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentForm.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentForm.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Experiments.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Experiments.tsx
Normal file → Executable file
134
management-dashboard-web-app/src/components/LiveCameraView.tsx
Executable file
134
management-dashboard-web-app/src/components/LiveCameraView.tsx
Executable file
@@ -0,0 +1,134 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
interface LiveCameraViewProps {
|
||||||
|
cameraName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LiveCameraView({ cameraName }: LiveCameraViewProps) {
|
||||||
|
const [isStreaming, setIsStreaming] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const imgRef = useRef<HTMLImageElement>(null)
|
||||||
|
|
||||||
|
const API_BASE = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000'
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
startStreaming()
|
||||||
|
return () => stopStreaming()
|
||||||
|
}, [cameraName])
|
||||||
|
|
||||||
|
const startStreaming = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
// Start the stream
|
||||||
|
const response = await fetch(`${API_BASE}/cameras/${cameraName}/start-stream`, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setIsStreaming(true)
|
||||||
|
// Set the stream source with timestamp to prevent caching
|
||||||
|
if (imgRef.current) {
|
||||||
|
imgRef.current.src = `${API_BASE}/cameras/${cameraName}/stream?t=${Date.now()}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to start stream: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Failed to start stream'
|
||||||
|
setError(errorMessage)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopStreaming = async () => {
|
||||||
|
try {
|
||||||
|
if (isStreaming) {
|
||||||
|
await fetch(`${API_BASE}/cameras/${cameraName}/stop-stream`, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
setIsStreaming(false)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error stopping stream:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageError = () => {
|
||||||
|
setError('Failed to load camera stream')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageLoad = () => {
|
||||||
|
setError(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||||
|
<div className="text-center text-white">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto"></div>
|
||||||
|
<p className="mt-4">Starting camera stream...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||||
|
<div className="text-center text-white">
|
||||||
|
<div className="bg-red-600 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl font-semibold mb-2">Stream Error</h2>
|
||||||
|
<p className="text-gray-300 mb-4">{error}</p>
|
||||||
|
<button
|
||||||
|
onClick={startStreaming}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-900 flex items-center justify-center">
|
||||||
|
<div className="relative">
|
||||||
|
{/* Camera Label */}
|
||||||
|
<div className="absolute top-4 left-4 z-10">
|
||||||
|
<div className="bg-black bg-opacity-75 text-white px-3 py-1 rounded-md text-sm font-medium">
|
||||||
|
{cameraName} - Live View
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Live Stream */}
|
||||||
|
<img
|
||||||
|
ref={imgRef}
|
||||||
|
alt={`Live stream from ${cameraName}`}
|
||||||
|
className="max-w-full max-h-screen object-contain"
|
||||||
|
onError={handleImageError}
|
||||||
|
onLoad={handleImageLoad}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Status Indicator */}
|
||||||
|
<div className="absolute bottom-4 right-4 z-10">
|
||||||
|
<div className="flex items-center space-x-2 bg-black bg-opacity-75 text-white px-3 py-1 rounded-md">
|
||||||
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
|
<span className="text-sm">LIVE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
management-dashboard-web-app/src/components/Login.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Login.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionDataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionDataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionLockManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionLockManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionPhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionPhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Sidebar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Sidebar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/TopNavbar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/TopNavbar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/UserManagement.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/UserManagement.tsx
Normal file → Executable file
27
management-dashboard-web-app/src/components/VisionSystem.tsx
Normal file → Executable file
27
management-dashboard-web-app/src/components/VisionSystem.tsx
Normal file → Executable file
@@ -196,9 +196,10 @@ const CamerasStatus = memo(({
|
|||||||
const hasSerial = !!camera.device_info?.serial_number
|
const hasSerial = !!camera.device_info?.serial_number
|
||||||
|
|
||||||
// Determine if camera is connected based on status
|
// Determine if camera is connected based on status
|
||||||
const isConnected = camera.status === 'available' || camera.status === 'connected'
|
const isConnected = camera.status === 'available' || camera.status === 'connected' || camera.status === 'streaming'
|
||||||
const hasError = camera.status === 'error'
|
const hasError = camera.status === 'error'
|
||||||
const statusText = camera.status || 'unknown'
|
const statusText = camera.status || 'unknown'
|
||||||
|
const isStreaming = camera.status === 'streaming'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={cameraName} className="border border-gray-200 rounded-lg p-4">
|
<div key={cameraName} className="border border-gray-200 rounded-lg p-4">
|
||||||
@@ -209,11 +210,12 @@ const CamerasStatus = memo(({
|
|||||||
<span className="text-gray-500 text-sm font-normal ml-2">({cameraName})</span>
|
<span className="text-gray-500 text-sm font-normal ml-2">({cameraName})</span>
|
||||||
)}
|
)}
|
||||||
</h4>
|
</h4>
|
||||||
<div className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${isConnected ? 'bg-green-100 text-green-800' :
|
<div className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${isStreaming ? 'bg-blue-100 text-blue-800' :
|
||||||
hasError ? 'bg-yellow-100 text-yellow-800' :
|
isConnected ? 'bg-green-100 text-green-800' :
|
||||||
'bg-red-100 text-red-800'
|
hasError ? 'bg-yellow-100 text-yellow-800' :
|
||||||
|
'bg-red-100 text-red-800'
|
||||||
}`}>
|
}`}>
|
||||||
{isConnected ? 'Connected' : hasError ? 'Error' : 'Disconnected'}
|
{isStreaming ? 'Streaming' : isConnected ? 'Connected' : hasError ? 'Error' : 'Disconnected'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -224,7 +226,8 @@ const CamerasStatus = memo(({
|
|||||||
hasError ? 'text-yellow-600' :
|
hasError ? 'text-yellow-600' :
|
||||||
'text-red-600'
|
'text-red-600'
|
||||||
}`}>
|
}`}>
|
||||||
{statusText.charAt(0).toUpperCase() + statusText.slice(1)}
|
{isStreaming ? 'Streaming' :
|
||||||
|
statusText.charAt(0).toUpperCase() + statusText.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -238,6 +241,16 @@ const CamerasStatus = memo(({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isStreaming && (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-500">Streaming:</span>
|
||||||
|
<span className="text-blue-600 font-medium flex items-center">
|
||||||
|
<div className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"></div>
|
||||||
|
Live
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{hasDeviceInfo && (
|
{hasDeviceInfo && (
|
||||||
<>
|
<>
|
||||||
{camera.device_info.model && (
|
{camera.device_info.model && (
|
||||||
@@ -923,7 +936,7 @@ export function VisionSystem() {
|
|||||||
|
|
||||||
{/* Notification */}
|
{/* Notification */}
|
||||||
{notification && (
|
{notification && (
|
||||||
<div className={`fixed top-4 right-4 z-50 p-4 rounded-md shadow-lg ${notification.type === 'success'
|
<div className={`fixed top-4 right-4 z-[999999] p-4 rounded-md shadow-lg ${notification.type === 'success'
|
||||||
? 'bg-green-50 border border-green-200 text-green-800'
|
? 'bg-green-50 border border-green-200 text-green-800'
|
||||||
: 'bg-red-50 border border-red-200 text-red-800'
|
: 'bg-red-50 border border-red-200 text-red-800'
|
||||||
}`}>
|
}`}>
|
||||||
|
|||||||
0
management-dashboard-web-app/src/features/video-streaming/VideoStreamingPage.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/VideoStreamingPage.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/ApiStatusIndicator.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/ApiStatusIndicator.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/Pagination.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/Pagination.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/PerformanceDashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/PerformanceDashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoCard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoCard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoDebugger.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoDebugger.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoErrorBoundary.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoErrorBoundary.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoList.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoList.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoPlayer.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoPlayer.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoThumbnail.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoThumbnail.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoInfo.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoInfo.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoList.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoList.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoPlayer.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoPlayer.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/types/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/types/index.ts
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user