Add comprehensive CSS styles for UI components and dark mode support

This commit is contained in:
rUv
2025-06-07 13:28:02 +00:00
parent 90f03bac7d
commit 6fe0d42f90
22 changed files with 5992 additions and 187 deletions

139
ui/services/api.service.js Normal file
View File

@@ -0,0 +1,139 @@
// API Service for WiFi-DensePose UI
import { API_CONFIG, buildApiUrl } from '../config/api.config.js';
export class ApiService {
constructor() {
this.authToken = null;
this.requestInterceptors = [];
this.responseInterceptors = [];
}
// Set authentication token
setAuthToken(token) {
this.authToken = token;
}
// Add request interceptor
addRequestInterceptor(interceptor) {
this.requestInterceptors.push(interceptor);
}
// Add response interceptor
addResponseInterceptor(interceptor) {
this.responseInterceptors.push(interceptor);
}
// Build headers for requests
getHeaders(customHeaders = {}) {
const headers = {
...API_CONFIG.DEFAULT_HEADERS,
...customHeaders
};
if (this.authToken) {
headers['Authorization'] = `Bearer ${this.authToken}`;
}
return headers;
}
// Process request through interceptors
async processRequest(url, options) {
let processedUrl = url;
let processedOptions = options;
for (const interceptor of this.requestInterceptors) {
const result = await interceptor(processedUrl, processedOptions);
processedUrl = result.url || processedUrl;
processedOptions = result.options || processedOptions;
}
return { url: processedUrl, options: processedOptions };
}
// Process response through interceptors
async processResponse(response, url) {
let processedResponse = response;
for (const interceptor of this.responseInterceptors) {
processedResponse = await interceptor(processedResponse, url);
}
return processedResponse;
}
// Generic request method
async request(url, options = {}) {
try {
// Process request through interceptors
const processed = await this.processRequest(url, options);
// Make the request
const response = await fetch(processed.url, {
...processed.options,
headers: this.getHeaders(processed.options.headers)
});
// Process response through interceptors
const processedResponse = await this.processResponse(response, url);
// Handle errors
if (!processedResponse.ok) {
const error = await processedResponse.json().catch(() => ({
message: `HTTP ${processedResponse.status}: ${processedResponse.statusText}`
}));
throw new Error(error.message || error.detail || 'Request failed');
}
// Parse JSON response
const data = await processedResponse.json().catch(() => null);
return data;
} catch (error) {
console.error('API Request Error:', error);
throw error;
}
}
// GET request
async get(endpoint, params = {}, options = {}) {
const url = buildApiUrl(endpoint, params);
return this.request(url, {
method: 'GET',
...options
});
}
// POST request
async post(endpoint, data = {}, options = {}) {
const url = buildApiUrl(endpoint);
return this.request(url, {
method: 'POST',
body: JSON.stringify(data),
...options
});
}
// PUT request
async put(endpoint, data = {}, options = {}) {
const url = buildApiUrl(endpoint);
return this.request(url, {
method: 'PUT',
body: JSON.stringify(data),
...options
});
}
// DELETE request
async delete(endpoint, options = {}) {
const url = buildApiUrl(endpoint);
return this.request(url, {
method: 'DELETE',
...options
});
}
}
// Create singleton instance
export const apiService = new ApiService();

View File

@@ -0,0 +1,138 @@
// Health Service for WiFi-DensePose UI
import { API_CONFIG } from '../config/api.config.js';
import { apiService } from './api.service.js';
export class HealthService {
constructor() {
this.healthCheckInterval = null;
this.healthSubscribers = [];
this.lastHealthStatus = null;
}
// Get system health
async getSystemHealth() {
const health = await apiService.get(API_CONFIG.ENDPOINTS.HEALTH.SYSTEM);
this.lastHealthStatus = health;
this.notifySubscribers(health);
return health;
}
// Check readiness
async checkReadiness() {
return apiService.get(API_CONFIG.ENDPOINTS.HEALTH.READY);
}
// Check liveness
async checkLiveness() {
return apiService.get(API_CONFIG.ENDPOINTS.HEALTH.LIVE);
}
// Get system metrics
async getSystemMetrics() {
return apiService.get(API_CONFIG.ENDPOINTS.HEALTH.METRICS);
}
// Get version info
async getVersion() {
return apiService.get(API_CONFIG.ENDPOINTS.HEALTH.VERSION);
}
// Get API info
async getApiInfo() {
return apiService.get(API_CONFIG.ENDPOINTS.INFO);
}
// Get API status
async getApiStatus() {
return apiService.get(API_CONFIG.ENDPOINTS.STATUS);
}
// Start periodic health checks
startHealthMonitoring(intervalMs = 30000) {
if (this.healthCheckInterval) {
console.warn('Health monitoring already active');
return;
}
// Initial check
this.getSystemHealth().catch(error => {
console.error('Initial health check failed:', error);
});
// Set up periodic checks
this.healthCheckInterval = setInterval(() => {
this.getSystemHealth().catch(error => {
console.error('Health check failed:', error);
this.notifySubscribers({
status: 'error',
error: error.message,
timestamp: new Date().toISOString()
});
});
}, intervalMs);
}
// Stop health monitoring
stopHealthMonitoring() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = null;
}
}
// Subscribe to health updates
subscribeToHealth(callback) {
this.healthSubscribers.push(callback);
// Send last known status if available
if (this.lastHealthStatus) {
callback(this.lastHealthStatus);
}
// Return unsubscribe function
return () => {
const index = this.healthSubscribers.indexOf(callback);
if (index > -1) {
this.healthSubscribers.splice(index, 1);
}
};
}
// Notify subscribers
notifySubscribers(health) {
this.healthSubscribers.forEach(callback => {
try {
callback(health);
} catch (error) {
console.error('Error in health subscriber:', error);
}
});
}
// Check if system is healthy
isSystemHealthy() {
if (!this.lastHealthStatus) {
return null;
}
return this.lastHealthStatus.status === 'healthy';
}
// Get component status
getComponentStatus(componentName) {
if (!this.lastHealthStatus?.components) {
return null;
}
return this.lastHealthStatus.components[componentName];
}
// Clean up
dispose() {
this.stopHealthMonitoring();
this.healthSubscribers = [];
this.lastHealthStatus = null;
}
}
// Create singleton instance
export const healthService = new HealthService();

303
ui/services/pose.service.js Normal file
View File

@@ -0,0 +1,303 @@
// Pose Service for WiFi-DensePose UI
import { API_CONFIG } from '../config/api.config.js';
import { apiService } from './api.service.js';
import { wsService } from './websocket.service.js';
export class PoseService {
constructor() {
this.streamConnection = null;
this.eventConnection = null;
this.poseSubscribers = [];
this.eventSubscribers = [];
}
// Get current pose estimation
async getCurrentPose(options = {}) {
const params = {
zone_ids: options.zoneIds?.join(','),
confidence_threshold: options.confidenceThreshold,
max_persons: options.maxPersons,
include_keypoints: options.includeKeypoints,
include_segmentation: options.includeSegmentation
};
// Remove undefined values
Object.keys(params).forEach(key =>
params[key] === undefined && delete params[key]
);
return apiService.get(API_CONFIG.ENDPOINTS.POSE.CURRENT, params);
}
// Analyze pose (requires auth)
async analyzePose(request) {
return apiService.post(API_CONFIG.ENDPOINTS.POSE.ANALYZE, request);
}
// Get zone occupancy
async getZoneOccupancy(zoneId) {
const endpoint = API_CONFIG.ENDPOINTS.POSE.ZONE_OCCUPANCY.replace('{zone_id}', zoneId);
return apiService.get(endpoint);
}
// Get zones summary
async getZonesSummary() {
return apiService.get(API_CONFIG.ENDPOINTS.POSE.ZONES_SUMMARY);
}
// Get historical data (requires auth)
async getHistoricalData(request) {
return apiService.post(API_CONFIG.ENDPOINTS.POSE.HISTORICAL, request);
}
// Get recent activities
async getActivities(options = {}) {
const params = {
zone_id: options.zoneId,
limit: options.limit || 50
};
// Remove undefined values
Object.keys(params).forEach(key =>
params[key] === undefined && delete params[key]
);
return apiService.get(API_CONFIG.ENDPOINTS.POSE.ACTIVITIES, params);
}
// Calibrate system (requires auth)
async calibrate() {
return apiService.post(API_CONFIG.ENDPOINTS.POSE.CALIBRATE);
}
// Get calibration status (requires auth)
async getCalibrationStatus() {
return apiService.get(API_CONFIG.ENDPOINTS.POSE.CALIBRATION_STATUS);
}
// Get pose statistics
async getStats(hours = 24) {
return apiService.get(API_CONFIG.ENDPOINTS.POSE.STATS, { hours });
}
// Start pose stream
startPoseStream(options = {}) {
if (this.streamConnection) {
console.warn('Pose stream already active');
return this.streamConnection;
}
const params = {
zone_ids: options.zoneIds?.join(','),
min_confidence: options.minConfidence || 0.5,
max_fps: options.maxFps || 30,
token: options.token || apiService.authToken
};
// Remove undefined values
Object.keys(params).forEach(key =>
params[key] === undefined && delete params[key]
);
this.streamConnection = wsService.connect(
API_CONFIG.ENDPOINTS.STREAM.WS_POSE,
params,
{
onOpen: () => {
console.log('Pose stream connected');
this.notifyPoseSubscribers({ type: 'connected' });
},
onMessage: (data) => {
this.handlePoseMessage(data);
},
onError: (error) => {
console.error('Pose stream error:', error);
this.notifyPoseSubscribers({ type: 'error', error });
},
onClose: () => {
console.log('Pose stream disconnected');
this.streamConnection = null;
this.notifyPoseSubscribers({ type: 'disconnected' });
}
}
);
return this.streamConnection;
}
// Stop pose stream
stopPoseStream() {
if (this.streamConnection) {
wsService.disconnect(this.streamConnection);
this.streamConnection = null;
}
}
// Subscribe to pose updates
subscribeToPoseUpdates(callback) {
this.poseSubscribers.push(callback);
// Return unsubscribe function
return () => {
const index = this.poseSubscribers.indexOf(callback);
if (index > -1) {
this.poseSubscribers.splice(index, 1);
}
};
}
// Handle pose stream messages
handlePoseMessage(data) {
const { type, payload } = data;
switch (type) {
case 'pose_data':
this.notifyPoseSubscribers({
type: 'pose_update',
data: payload
});
break;
case 'historical_data':
this.notifyPoseSubscribers({
type: 'historical_update',
data: payload
});
break;
case 'zone_statistics':
this.notifyPoseSubscribers({
type: 'zone_stats',
data: payload
});
break;
case 'system_event':
this.notifyPoseSubscribers({
type: 'system_event',
data: payload
});
break;
default:
console.log('Unknown pose message type:', type);
}
}
// Notify pose subscribers
notifyPoseSubscribers(update) {
this.poseSubscribers.forEach(callback => {
try {
callback(update);
} catch (error) {
console.error('Error in pose subscriber:', error);
}
});
}
// Start event stream
startEventStream(options = {}) {
if (this.eventConnection) {
console.warn('Event stream already active');
return this.eventConnection;
}
const params = {
event_types: options.eventTypes?.join(','),
zone_ids: options.zoneIds?.join(','),
token: options.token || apiService.authToken
};
// Remove undefined values
Object.keys(params).forEach(key =>
params[key] === undefined && delete params[key]
);
this.eventConnection = wsService.connect(
API_CONFIG.ENDPOINTS.STREAM.WS_EVENTS,
params,
{
onOpen: () => {
console.log('Event stream connected');
this.notifyEventSubscribers({ type: 'connected' });
},
onMessage: (data) => {
this.handleEventMessage(data);
},
onError: (error) => {
console.error('Event stream error:', error);
this.notifyEventSubscribers({ type: 'error', error });
},
onClose: () => {
console.log('Event stream disconnected');
this.eventConnection = null;
this.notifyEventSubscribers({ type: 'disconnected' });
}
}
);
return this.eventConnection;
}
// Stop event stream
stopEventStream() {
if (this.eventConnection) {
wsService.disconnect(this.eventConnection);
this.eventConnection = null;
}
}
// Subscribe to events
subscribeToEvents(callback) {
this.eventSubscribers.push(callback);
// Return unsubscribe function
return () => {
const index = this.eventSubscribers.indexOf(callback);
if (index > -1) {
this.eventSubscribers.splice(index, 1);
}
};
}
// Handle event stream messages
handleEventMessage(data) {
this.notifyEventSubscribers({
type: 'event',
data
});
}
// Notify event subscribers
notifyEventSubscribers(update) {
this.eventSubscribers.forEach(callback => {
try {
callback(update);
} catch (error) {
console.error('Error in event subscriber:', error);
}
});
}
// Update stream configuration
updateStreamConfig(connectionId, config) {
wsService.sendCommand(connectionId, 'update_config', config);
}
// Get stream status
requestStreamStatus(connectionId) {
wsService.sendCommand(connectionId, 'get_status');
}
// Clean up
dispose() {
this.stopPoseStream();
this.stopEventStream();
this.poseSubscribers = [];
this.eventSubscribers = [];
}
}
// Create singleton instance
export const poseService = new PoseService();

View File

@@ -0,0 +1,59 @@
// Stream Service for WiFi-DensePose UI
import { API_CONFIG } from '../config/api.config.js';
import { apiService } from './api.service.js';
export class StreamService {
// Get streaming status
async getStatus() {
return apiService.get(API_CONFIG.ENDPOINTS.STREAM.STATUS);
}
// Start streaming (requires auth)
async start() {
return apiService.post(API_CONFIG.ENDPOINTS.STREAM.START);
}
// Stop streaming (requires auth)
async stop() {
return apiService.post(API_CONFIG.ENDPOINTS.STREAM.STOP);
}
// Get connected clients (requires auth)
async getClients() {
return apiService.get(API_CONFIG.ENDPOINTS.STREAM.CLIENTS);
}
// Disconnect a client (requires auth)
async disconnectClient(clientId) {
const endpoint = API_CONFIG.ENDPOINTS.STREAM.DISCONNECT_CLIENT.replace('{client_id}', clientId);
return apiService.delete(endpoint);
}
// Broadcast message (requires auth)
async broadcast(message, options = {}) {
const params = {
stream_type: options.streamType,
zone_ids: options.zoneIds?.join(',')
};
// Remove undefined values
Object.keys(params).forEach(key =>
params[key] === undefined && delete params[key]
);
return apiService.post(
API_CONFIG.ENDPOINTS.STREAM.BROADCAST,
message,
{ params }
);
}
// Get streaming metrics
async getMetrics() {
return apiService.get(API_CONFIG.ENDPOINTS.STREAM.METRICS);
}
}
// Create singleton instance
export const streamService = new StreamService();

View File

@@ -0,0 +1,305 @@
// WebSocket Service for WiFi-DensePose UI
import { API_CONFIG, buildWsUrl } from '../config/api.config.js';
export class WebSocketService {
constructor() {
this.connections = new Map();
this.messageHandlers = new Map();
this.reconnectAttempts = new Map();
}
// Connect to WebSocket endpoint
connect(endpoint, params = {}, handlers = {}) {
const url = buildWsUrl(endpoint, params);
// Check if already connected
if (this.connections.has(url)) {
console.warn(`Already connected to ${url}`);
return this.connections.get(url);
}
// Create WebSocket connection
const ws = new WebSocket(url);
const connectionId = this.generateId();
// Store connection
this.connections.set(url, {
id: connectionId,
ws,
url,
handlers,
status: 'connecting',
lastPing: null,
reconnectTimer: null
});
// Set up event handlers
this.setupEventHandlers(url, ws, handlers);
// Start ping interval
this.startPingInterval(url);
return connectionId;
}
// Set up WebSocket event handlers
setupEventHandlers(url, ws, handlers) {
const connection = this.connections.get(url);
ws.onopen = (event) => {
console.log(`WebSocket connected: ${url}`);
connection.status = 'connected';
this.reconnectAttempts.set(url, 0);
if (handlers.onOpen) {
handlers.onOpen(event);
}
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// Handle different message types
this.handleMessage(url, data);
if (handlers.onMessage) {
handlers.onMessage(data);
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
ws.onerror = (event) => {
console.error(`WebSocket error: ${url}`, event);
connection.status = 'error';
if (handlers.onError) {
handlers.onError(event);
}
};
ws.onclose = (event) => {
console.log(`WebSocket closed: ${url}`);
connection.status = 'closed';
// Clear ping interval
this.clearPingInterval(url);
if (handlers.onClose) {
handlers.onClose(event);
}
// Attempt reconnection if not intentionally closed
if (!event.wasClean && this.shouldReconnect(url)) {
this.scheduleReconnect(url);
} else {
this.connections.delete(url);
}
};
}
// Handle incoming messages
handleMessage(url, data) {
const { type, payload } = data;
// Handle system messages
switch (type) {
case 'pong':
this.handlePong(url);
break;
case 'connection_established':
console.log('Connection established:', payload);
break;
case 'error':
console.error('WebSocket error message:', payload);
break;
}
// Call registered message handlers
const handlers = this.messageHandlers.get(url) || [];
handlers.forEach(handler => handler(data));
}
// Send message through WebSocket
send(connectionId, message) {
const connection = this.findConnectionById(connectionId);
if (!connection) {
throw new Error(`Connection ${connectionId} not found`);
}
if (connection.status !== 'connected') {
throw new Error(`Connection ${connectionId} is not connected`);
}
const data = typeof message === 'string'
? message
: JSON.stringify(message);
connection.ws.send(data);
}
// Send command message
sendCommand(connectionId, command, payload = {}) {
this.send(connectionId, {
type: command,
payload,
timestamp: new Date().toISOString()
});
}
// Register message handler
onMessage(connectionId, handler) {
const connection = this.findConnectionById(connectionId);
if (!connection) {
throw new Error(`Connection ${connectionId} not found`);
}
if (!this.messageHandlers.has(connection.url)) {
this.messageHandlers.set(connection.url, []);
}
this.messageHandlers.get(connection.url).push(handler);
// Return unsubscribe function
return () => {
const handlers = this.messageHandlers.get(connection.url);
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
};
}
// Disconnect WebSocket
disconnect(connectionId) {
const connection = this.findConnectionById(connectionId);
if (!connection) {
return;
}
// Clear reconnection timer
if (connection.reconnectTimer) {
clearTimeout(connection.reconnectTimer);
}
// Clear ping interval
this.clearPingInterval(connection.url);
// Close WebSocket
if (connection.ws.readyState === WebSocket.OPEN) {
connection.ws.close(1000, 'Client disconnect');
}
// Clean up
this.connections.delete(connection.url);
this.messageHandlers.delete(connection.url);
this.reconnectAttempts.delete(connection.url);
}
// Disconnect all WebSockets
disconnectAll() {
const connectionIds = Array.from(this.connections.values()).map(c => c.id);
connectionIds.forEach(id => this.disconnect(id));
}
// Ping/Pong handling
startPingInterval(url) {
const connection = this.connections.get(url);
if (!connection) return;
connection.pingInterval = setInterval(() => {
if (connection.status === 'connected') {
this.sendPing(url);
}
}, API_CONFIG.WS_CONFIG.PING_INTERVAL);
}
clearPingInterval(url) {
const connection = this.connections.get(url);
if (connection && connection.pingInterval) {
clearInterval(connection.pingInterval);
}
}
sendPing(url) {
const connection = this.connections.get(url);
if (connection && connection.status === 'connected') {
connection.lastPing = Date.now();
connection.ws.send(JSON.stringify({ type: 'ping' }));
}
}
handlePong(url) {
const connection = this.connections.get(url);
if (connection) {
const latency = Date.now() - connection.lastPing;
console.log(`Pong received. Latency: ${latency}ms`);
}
}
// Reconnection logic
shouldReconnect(url) {
const attempts = this.reconnectAttempts.get(url) || 0;
return attempts < API_CONFIG.WS_CONFIG.MAX_RECONNECT_ATTEMPTS;
}
scheduleReconnect(url) {
const connection = this.connections.get(url);
if (!connection) return;
const attempts = this.reconnectAttempts.get(url) || 0;
const delay = API_CONFIG.WS_CONFIG.RECONNECT_DELAY * Math.pow(2, attempts);
console.log(`Scheduling reconnect in ${delay}ms (attempt ${attempts + 1})`);
connection.reconnectTimer = setTimeout(() => {
this.reconnectAttempts.set(url, attempts + 1);
// Get original parameters
const params = new URL(url).searchParams;
const paramsObj = Object.fromEntries(params);
const endpoint = url.replace(/^wss?:\/\/[^\/]+/, '').split('?')[0];
// Attempt reconnection
this.connect(endpoint, paramsObj, connection.handlers);
}, delay);
}
// Utility methods
findConnectionById(connectionId) {
for (const connection of this.connections.values()) {
if (connection.id === connectionId) {
return connection;
}
}
return null;
}
generateId() {
return `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
getConnectionStatus(connectionId) {
const connection = this.findConnectionById(connectionId);
return connection ? connection.status : 'disconnected';
}
getActiveConnections() {
return Array.from(this.connections.values()).map(conn => ({
id: conn.id,
url: conn.url,
status: conn.status
}));
}
}
// Create singleton instance
export const wsService = new WebSocketService();