updates
This commit is contained in:
21
ui/README.md
21
ui/README.md
@@ -34,6 +34,12 @@ ui/
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
### Smart Backend Detection
|
||||
- **Automatic Detection**: Automatically detects if your FastAPI backend is running
|
||||
- **Real Backend Priority**: Always uses the real backend when available
|
||||
- **Mock Fallback**: Falls back to mock server only when backend is unavailable
|
||||
- **Testing Mode**: Can force mock mode for testing and development
|
||||
|
||||
### Real-time Dashboard
|
||||
- Live system health monitoring
|
||||
- Real-time pose detection statistics
|
||||
@@ -281,6 +287,14 @@ apiService.addResponseInterceptor(async (response, url) => {
|
||||
## 🚀 Deployment
|
||||
|
||||
### Development
|
||||
|
||||
**Option 1: Use the startup script**
|
||||
```bash
|
||||
cd /workspaces/wifi-densepose/ui
|
||||
./start-ui.sh
|
||||
```
|
||||
|
||||
**Option 2: Manual setup**
|
||||
```bash
|
||||
# First, start your FastAPI backend (runs on port 8000)
|
||||
wifi-densepose start
|
||||
@@ -294,9 +308,14 @@ python -m http.server 3000
|
||||
npx http-server . -p 3000
|
||||
|
||||
# Open the UI at http://localhost:3000
|
||||
# The UI will connect to the FastAPI backend at http://localhost:8000
|
||||
# The UI will automatically detect and connect to your backend
|
||||
```
|
||||
|
||||
### Backend Detection Behavior
|
||||
- **Real Backend Available**: UI connects to `http://localhost:8000` and shows ✅ "Connected to real backend"
|
||||
- **Backend Unavailable**: UI automatically uses mock server and shows ⚠️ "Mock server active - testing mode"
|
||||
- **Force Mock Mode**: Set `API_CONFIG.MOCK_SERVER.ENABLED = true` for testing
|
||||
|
||||
### Production
|
||||
1. Configure `API_CONFIG.BASE_URL` for your backend
|
||||
2. Set up HTTPS for WebSocket connections
|
||||
|
||||
54
ui/app.js
54
ui/app.js
@@ -7,6 +7,7 @@ import { LiveDemoTab } from './components/LiveDemoTab.js';
|
||||
import { apiService } from './services/api.service.js';
|
||||
import { wsService } from './services/websocket.service.js';
|
||||
import { healthService } from './services/health.service.js';
|
||||
import { backendDetector } from './utils/backend-detector.js';
|
||||
|
||||
class WiFiDensePoseApp {
|
||||
constructor() {
|
||||
@@ -51,13 +52,30 @@ class WiFiDensePoseApp {
|
||||
return response;
|
||||
});
|
||||
|
||||
// Check API availability
|
||||
try {
|
||||
const health = await healthService.checkLiveness();
|
||||
console.log('API is available:', health);
|
||||
} catch (error) {
|
||||
console.error('API is not available:', error);
|
||||
throw new Error('API is not available. Please ensure the backend is running.');
|
||||
// Detect backend availability and initialize accordingly
|
||||
const useMock = await backendDetector.shouldUseMockServer();
|
||||
|
||||
if (useMock) {
|
||||
console.log('🧪 Initializing with mock server for testing');
|
||||
// Import and start mock server only when needed
|
||||
const { mockServer } = await import('./utils/mock-server.js');
|
||||
mockServer.start();
|
||||
|
||||
// Show notification to user
|
||||
this.showBackendStatus('Mock server active - testing mode', 'warning');
|
||||
} else {
|
||||
console.log('🔌 Initializing with real backend');
|
||||
|
||||
// Verify backend is actually working
|
||||
try {
|
||||
const health = await healthService.checkLiveness();
|
||||
console.log('✅ Backend is available and responding:', health);
|
||||
this.showBackendStatus('Connected to real backend', 'success');
|
||||
} catch (error) {
|
||||
console.error('❌ Backend check failed:', error);
|
||||
this.showBackendStatus('Backend connection failed', 'error');
|
||||
// Don't throw - let the app continue and retry later
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +213,28 @@ class WiFiDensePoseApp {
|
||||
});
|
||||
}
|
||||
|
||||
// Show backend status notification
|
||||
showBackendStatus(message, type) {
|
||||
// Create status notification if it doesn't exist
|
||||
let statusToast = document.getElementById('backendStatusToast');
|
||||
if (!statusToast) {
|
||||
statusToast = document.createElement('div');
|
||||
statusToast.id = 'backendStatusToast';
|
||||
statusToast.className = 'backend-status-toast';
|
||||
document.body.appendChild(statusToast);
|
||||
}
|
||||
|
||||
statusToast.textContent = message;
|
||||
statusToast.className = `backend-status-toast ${type}`;
|
||||
statusToast.classList.add('show');
|
||||
|
||||
// Auto-hide success messages, keep warnings and errors longer
|
||||
const timeout = type === 'success' ? 3000 : 8000;
|
||||
setTimeout(() => {
|
||||
statusToast.classList.remove('show');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// Show global error message
|
||||
showGlobalError(message) {
|
||||
// Create error toast if it doesn't exist
|
||||
|
||||
@@ -6,6 +6,12 @@ export const API_CONFIG = {
|
||||
WS_PREFIX: 'ws://',
|
||||
WSS_PREFIX: 'wss://',
|
||||
|
||||
// Mock server configuration (only for testing)
|
||||
MOCK_SERVER: {
|
||||
ENABLED: false, // Set to true only for testing without backend
|
||||
AUTO_DETECT: true, // Automatically detect if backend is available
|
||||
},
|
||||
|
||||
// API Endpoints
|
||||
ENDPOINTS: {
|
||||
// Root & Info
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// API Service for WiFi-DensePose UI
|
||||
|
||||
import { API_CONFIG, buildApiUrl } from '../config/api.config.js';
|
||||
import { backendDetector } from '../utils/backend-detector.js';
|
||||
|
||||
export class ApiService {
|
||||
constructor() {
|
||||
@@ -69,8 +70,15 @@ export class ApiService {
|
||||
// Process request through interceptors
|
||||
const processed = await this.processRequest(url, options);
|
||||
|
||||
// Determine the correct base URL (real backend vs mock)
|
||||
let finalUrl = processed.url;
|
||||
if (processed.url.startsWith(API_CONFIG.BASE_URL)) {
|
||||
const baseUrl = await backendDetector.getBaseUrl();
|
||||
finalUrl = processed.url.replace(API_CONFIG.BASE_URL, baseUrl);
|
||||
}
|
||||
|
||||
// Make the request
|
||||
const response = await fetch(processed.url, {
|
||||
const response = await fetch(finalUrl, {
|
||||
...processed.options,
|
||||
headers: this.getHeaders(processed.options.headers)
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// WebSocket Service for WiFi-DensePose UI
|
||||
|
||||
import { API_CONFIG, buildWsUrl } from '../config/api.config.js';
|
||||
import { backendDetector } from '../utils/backend-detector.js';
|
||||
|
||||
export class WebSocketService {
|
||||
constructor() {
|
||||
@@ -10,8 +11,18 @@ export class WebSocketService {
|
||||
}
|
||||
|
||||
// Connect to WebSocket endpoint
|
||||
connect(endpoint, params = {}, handlers = {}) {
|
||||
const url = buildWsUrl(endpoint, params);
|
||||
async connect(endpoint, params = {}, handlers = {}) {
|
||||
// Determine if we should use mock WebSockets
|
||||
const useMock = await backendDetector.shouldUseMockServer();
|
||||
|
||||
let url;
|
||||
if (useMock) {
|
||||
// Use mock WebSocket URL (served from same origin as UI)
|
||||
url = buildWsUrl(endpoint, params).replace('localhost:8000', window.location.host);
|
||||
} else {
|
||||
// Use real backend WebSocket URL
|
||||
url = buildWsUrl(endpoint, params);
|
||||
}
|
||||
|
||||
// Check if already connected
|
||||
if (this.connections.has(url)) {
|
||||
|
||||
36
ui/style.css
36
ui/style.css
@@ -1573,6 +1573,42 @@ canvas {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Backend status toast */
|
||||
.backend-status-toast {
|
||||
position: fixed;
|
||||
top: var(--space-24);
|
||||
right: var(--space-24);
|
||||
padding: var(--space-12) var(--space-20);
|
||||
border-radius: var(--radius-base);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1001;
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.backend-status-toast.show {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.backend-status-toast.success {
|
||||
background: var(--color-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.backend-status-toast.warning {
|
||||
background: var(--color-warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.backend-status-toast.error {
|
||||
background: var(--color-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Tab badge */
|
||||
.tab-badge {
|
||||
display: inline-block;
|
||||
|
||||
@@ -95,8 +95,8 @@
|
||||
<!-- Test Controls Panel -->
|
||||
<div class="test-controls">
|
||||
<h3>Integration Tests</h3>
|
||||
<button class="test-button" onclick="startMockServer()">Start Mock Server</button>
|
||||
<button class="test-button danger" onclick="stopMockServer()">Stop Mock Server</button>
|
||||
<button class="test-button" onclick="toggleMockMode()">Toggle Mock Mode</button>
|
||||
<button class="test-button" onclick="checkBackendStatus()">Check Backend Status</button>
|
||||
<button class="test-button" onclick="testHealthAPI()">Test Health API</button>
|
||||
<button class="test-button" onclick="testPoseAPI()">Test Pose API</button>
|
||||
<button class="test-button" onclick="testWebSocketStream()">Test WebSocket</button>
|
||||
@@ -376,34 +376,60 @@
|
||||
<script type="module">
|
||||
import { mockServer } from '../utils/mock-server.js';
|
||||
import { WiFiDensePoseApp } from '../app.js';
|
||||
import { API_CONFIG } from '../config/api.config.js';
|
||||
import { backendDetector } from '../utils/backend-detector.js';
|
||||
|
||||
// Global test functions
|
||||
window.mockServer = mockServer;
|
||||
window.app = null;
|
||||
|
||||
window.startMockServer = () => {
|
||||
window.toggleMockMode = async () => {
|
||||
try {
|
||||
mockServer.start();
|
||||
updateMockIndicator(true);
|
||||
showTestStatus('Mock server started successfully', 'success');
|
||||
// Toggle mock mode
|
||||
API_CONFIG.MOCK_SERVER.ENABLED = !API_CONFIG.MOCK_SERVER.ENABLED;
|
||||
|
||||
// Initialize app if not already done
|
||||
// Force backend detector to recheck
|
||||
backendDetector.forceCheck();
|
||||
|
||||
if (API_CONFIG.MOCK_SERVER.ENABLED) {
|
||||
mockServer.start();
|
||||
updateMockIndicator(true);
|
||||
showTestStatus('Mock mode enabled - using test data', 'success');
|
||||
} else {
|
||||
mockServer.stop();
|
||||
updateMockIndicator(false);
|
||||
showTestStatus('Mock mode disabled - using real backend', 'info');
|
||||
}
|
||||
|
||||
// Reinitialize app with new configuration
|
||||
if (!window.app) {
|
||||
window.app = new WiFiDensePoseApp();
|
||||
window.app.init();
|
||||
await window.app.init();
|
||||
}
|
||||
} catch (error) {
|
||||
showTestStatus(`Failed to start mock server: ${error.message}`, 'error');
|
||||
showTestStatus(`Failed to toggle mock mode: ${error.message}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.stopMockServer = () => {
|
||||
window.checkBackendStatus = async () => {
|
||||
try {
|
||||
mockServer.stop();
|
||||
updateMockIndicator(false);
|
||||
showTestStatus('Mock server stopped', 'info');
|
||||
showTestStatus('Checking backend status...', 'info');
|
||||
|
||||
const isAvailable = await backendDetector.checkBackendAvailability();
|
||||
const useMock = await backendDetector.shouldUseMockServer();
|
||||
|
||||
if (isAvailable && !useMock) {
|
||||
showTestStatus('✅ Real backend is available and being used', 'success');
|
||||
updateMockIndicator(false);
|
||||
} else if (useMock) {
|
||||
showTestStatus('🧪 Using mock server (testing mode)', 'success');
|
||||
updateMockIndicator(true);
|
||||
} else {
|
||||
showTestStatus('❌ Backend unavailable, mock server available', 'error');
|
||||
updateMockIndicator(false);
|
||||
}
|
||||
} catch (error) {
|
||||
showTestStatus(`Failed to stop mock server: ${error.message}`, 'error');
|
||||
showTestStatus(`Backend check failed: ${error.message}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -537,9 +563,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start mock server on load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
startMockServer();
|
||||
// Auto-check backend status on load
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await checkBackendStatus();
|
||||
|
||||
// Initialize app
|
||||
if (!window.app) {
|
||||
window.app = new WiFiDensePoseApp();
|
||||
await window.app.init();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
101
ui/utils/backend-detector.js
Normal file
101
ui/utils/backend-detector.js
Normal file
@@ -0,0 +1,101 @@
|
||||
// Backend Detection Utility
|
||||
|
||||
import { API_CONFIG } from '../config/api.config.js';
|
||||
|
||||
export class BackendDetector {
|
||||
constructor() {
|
||||
this.isBackendAvailable = null;
|
||||
this.lastCheck = 0;
|
||||
this.checkInterval = 30000; // Check every 30 seconds
|
||||
}
|
||||
|
||||
// Check if the real backend is available
|
||||
async checkBackendAvailability() {
|
||||
const now = Date.now();
|
||||
|
||||
// Use cached result if recent
|
||||
if (this.isBackendAvailable !== null && (now - this.lastCheck) < this.checkInterval) {
|
||||
return this.isBackendAvailable;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🔍 Checking backend availability...');
|
||||
|
||||
// Try to connect to the health endpoint with a short timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
|
||||
|
||||
const response = await fetch(`${API_CONFIG.BASE_URL}/health/live`, {
|
||||
method: 'GET',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (response.ok) {
|
||||
this.isBackendAvailable = true;
|
||||
this.lastCheck = now;
|
||||
console.log('✅ Real backend is available');
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`Backend responded with status ${response.status}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.isBackendAvailable = false;
|
||||
this.lastCheck = now;
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
console.log('⏱️ Backend check timed out - assuming unavailable');
|
||||
} else {
|
||||
console.log(`❌ Backend unavailable: ${error.message}`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if mock server should be used
|
||||
async shouldUseMockServer() {
|
||||
// If mock is explicitly enabled, always use it
|
||||
if (API_CONFIG.MOCK_SERVER.ENABLED) {
|
||||
console.log('🧪 Using mock server (explicitly enabled)');
|
||||
return true;
|
||||
}
|
||||
|
||||
// If auto-detection is disabled, never use mock
|
||||
if (!API_CONFIG.MOCK_SERVER.AUTO_DETECT) {
|
||||
console.log('🔌 Using real backend (auto-detection disabled)');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if backend is available
|
||||
const backendAvailable = await this.checkBackendAvailability();
|
||||
|
||||
if (backendAvailable) {
|
||||
console.log('🔌 Using real backend (detected and available)');
|
||||
return false;
|
||||
} else {
|
||||
console.log('🧪 Using mock server (backend unavailable)');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the appropriate base URL
|
||||
async getBaseUrl() {
|
||||
const useMock = await this.shouldUseMockServer();
|
||||
return useMock ? window.location.origin : API_CONFIG.BASE_URL;
|
||||
}
|
||||
|
||||
// Force a fresh check
|
||||
forceCheck() {
|
||||
this.isBackendAvailable = null;
|
||||
this.lastCheck = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const backendDetector = new BackendDetector();
|
||||
Reference in New Issue
Block a user