Add API service layer, authentication store, and composables
- Implement API service modules for sensors, rooms, analytics, health, and auth - Add Pinia auth store for JWT token management and validation - Create Vue composables for API integration and state management - Update settings and AI optimization views for code style and connection URLs - Add test-websocket.html for local WebSocket testing
This commit is contained in:
56
src/services/analyticsApi.ts
Normal file
56
src/services/analyticsApi.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Analytics API Service
|
||||
* Handles analytics and reporting API calls
|
||||
*/
|
||||
import {
|
||||
apiClient,
|
||||
type AnalyticsSummary,
|
||||
type EnergyTrends,
|
||||
type RoomComparison,
|
||||
type SystemEvent,
|
||||
} from './api'
|
||||
|
||||
export const analyticsApi = {
|
||||
/**
|
||||
* Get analytics summary for the specified time period
|
||||
*/
|
||||
async getAnalyticsSummary(hours: number = 24): Promise<AnalyticsSummary> {
|
||||
return apiClient.get<AnalyticsSummary>('/api/v1/analytics/summary', { hours })
|
||||
},
|
||||
|
||||
/**
|
||||
* Get energy consumption trends
|
||||
*/
|
||||
async getEnergyTrends(hours: number = 168): Promise<EnergyTrends> {
|
||||
return apiClient.get<EnergyTrends>('/api/v1/analytics/trends', { hours })
|
||||
},
|
||||
|
||||
/**
|
||||
* Get room-by-room comparison analytics
|
||||
*/
|
||||
async getRoomComparison(hours: number = 24): Promise<RoomComparison> {
|
||||
return apiClient.get<RoomComparison>('/api/v1/analytics/rooms', { hours })
|
||||
},
|
||||
|
||||
/**
|
||||
* Get recent system events and alerts
|
||||
*/
|
||||
async getEvents(params?: {
|
||||
severity?: string
|
||||
event_type?: string
|
||||
hours?: number
|
||||
limit?: number
|
||||
}): Promise<{
|
||||
events: SystemEvent[]
|
||||
count: number
|
||||
period_hours: number
|
||||
}> {
|
||||
return apiClient.get<{
|
||||
events: SystemEvent[]
|
||||
count: number
|
||||
period_hours: number
|
||||
}>('/api/v1/events', params)
|
||||
},
|
||||
}
|
||||
|
||||
export default analyticsApi
|
||||
336
src/services/api.ts
Normal file
336
src/services/api.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* API Service Layer for Energy Monitoring Dashboard
|
||||
* Handles all backend API communications
|
||||
*/
|
||||
|
||||
// Base configuration
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
|
||||
|
||||
// API Response types
|
||||
export interface ApiResponse<T = any> {
|
||||
data: T
|
||||
total_count?: number
|
||||
query?: any
|
||||
execution_time_ms?: number
|
||||
}
|
||||
|
||||
export interface HealthCheck {
|
||||
status: 'healthy' | 'degraded'
|
||||
mongodb_connected: boolean
|
||||
redis_connected: boolean
|
||||
total_sensors: number
|
||||
active_sensors: number
|
||||
total_readings: number
|
||||
uptime_seconds: number
|
||||
}
|
||||
|
||||
export interface SystemStatus {
|
||||
timestamp: number
|
||||
uptime_seconds: number
|
||||
active_websocket_connections: number
|
||||
database_stats: {
|
||||
total_sensors: number
|
||||
active_sensors: number
|
||||
total_readings: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DataQuery {
|
||||
sensor_ids?: string[]
|
||||
rooms?: string[]
|
||||
sensor_types?: SensorType[]
|
||||
start_time?: number
|
||||
end_time?: number
|
||||
limit?: number
|
||||
offset?: number
|
||||
sort_by?: string
|
||||
sort_order?: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
export interface DataResponse {
|
||||
data: SensorReading[]
|
||||
total_count: number
|
||||
query: DataQuery
|
||||
execution_time_ms: number
|
||||
}
|
||||
|
||||
export interface SensorReading {
|
||||
_id?: string
|
||||
sensor_id: string
|
||||
room?: string
|
||||
sensor_type: string
|
||||
timestamp: number
|
||||
created_at?: string
|
||||
energy?: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
co2?: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
temperature?: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
humidity?: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface SensorInfo {
|
||||
sensor_id: string
|
||||
sensor_type: SensorType
|
||||
room?: string
|
||||
status: SensorStatus
|
||||
first_seen: number
|
||||
last_seen: number
|
||||
total_readings: number
|
||||
latest_values?: Record<string, any>
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface RoomInfo {
|
||||
room: string
|
||||
sensor_count: number
|
||||
sensor_types: SensorType[]
|
||||
latest_metrics?: {
|
||||
energy?: { current: number; unit: string }
|
||||
co2?: { current: number; unit: string }
|
||||
temperature?: { current: number; unit: string }
|
||||
humidity?: { current: number; unit: string }
|
||||
}
|
||||
last_updated?: number
|
||||
}
|
||||
|
||||
export interface RoomData {
|
||||
room: string
|
||||
sensors: string[]
|
||||
data: SensorReading[]
|
||||
aggregated_metrics: {
|
||||
energy?: { total: number; average: number; current: number; unit: string }
|
||||
co2?: { average: number; max: number; current: number; unit: string }
|
||||
temperature?: { average: number; min: number; max: number; current: number; unit: string }
|
||||
}
|
||||
execution_time_ms?: number
|
||||
}
|
||||
|
||||
export interface AnalyticsSummary {
|
||||
period_hours: number
|
||||
total_energy_consumption: { value: number; unit: string }
|
||||
average_power: { value: number; unit: string }
|
||||
peak_power: { value: number; unit: string; timestamp: number }
|
||||
sensor_count: number
|
||||
room_count: number
|
||||
co2_analysis?: {
|
||||
average_ppm: number
|
||||
max_ppm: number
|
||||
rooms_above_threshold: string[]
|
||||
}
|
||||
top_consuming_sensors: Array<{
|
||||
sensor_id: string
|
||||
room: string
|
||||
consumption: number
|
||||
unit: string
|
||||
}>
|
||||
top_consuming_rooms: Array<{
|
||||
room: string
|
||||
consumption: number
|
||||
unit: string
|
||||
sensor_count: number
|
||||
}>
|
||||
}
|
||||
|
||||
export interface EnergyTrends {
|
||||
period_hours: number
|
||||
hourly_consumption: Array<{
|
||||
hour: string
|
||||
consumption: number
|
||||
unit: string
|
||||
}>
|
||||
daily_averages: Array<{
|
||||
date: string
|
||||
average_consumption: number
|
||||
unit: string
|
||||
}>
|
||||
trend_analysis: {
|
||||
trend: 'increasing' | 'decreasing' | 'stable'
|
||||
percentage_change: number
|
||||
prediction_next_24h: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface RoomComparison {
|
||||
period_hours: number
|
||||
rooms: Array<{
|
||||
room: string
|
||||
total_consumption: number
|
||||
average_consumption: number
|
||||
peak_consumption: number
|
||||
sensor_count: number
|
||||
efficiency_rating: 'excellent' | 'good' | 'average' | 'poor'
|
||||
unit: string
|
||||
}>
|
||||
insights: {
|
||||
most_efficient: string
|
||||
least_efficient: string
|
||||
total_consumption: number
|
||||
average_per_room: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SystemEvent {
|
||||
_id: string
|
||||
timestamp: number
|
||||
event_type: string
|
||||
severity: 'info' | 'warning' | 'error' | 'critical'
|
||||
message: string
|
||||
details?: Record<string, any>
|
||||
sensor_id?: string
|
||||
room?: string
|
||||
}
|
||||
|
||||
export enum SensorType {
|
||||
ENERGY = 'energy',
|
||||
CO2 = 'co2',
|
||||
TEMPERATURE = 'temperature',
|
||||
HUMIDITY = 'humidity',
|
||||
HVAC = 'hvac',
|
||||
LIGHTING = 'lighting',
|
||||
SECURITY = 'security',
|
||||
}
|
||||
|
||||
export enum SensorStatus {
|
||||
ONLINE = 'online',
|
||||
OFFLINE = 'offline',
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
// HTTP Client class
|
||||
class ApiClient {
|
||||
private baseUrl: string
|
||||
|
||||
constructor(baseUrl: string = API_BASE_URL) {
|
||||
this.baseUrl = baseUrl
|
||||
}
|
||||
|
||||
private getAuthHeaders(): Record<string, string> {
|
||||
// Dynamically get auth headers to avoid circular imports
|
||||
try {
|
||||
const authStore = (window as any).__AUTH_STORE__
|
||||
if (authStore && typeof authStore.getAuthHeader === 'function') {
|
||||
return authStore.getAuthHeader()
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not get auth headers:', error)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||
const url = `${this.baseUrl}${endpoint}`
|
||||
|
||||
const authHeaders = this.getAuthHeaders()
|
||||
const config: RequestInit = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...authHeaders,
|
||||
...options.headers,
|
||||
},
|
||||
...options,
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
throw new Error(`HTTP ${response.status}: ${errorData.detail || response.statusText}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`API request failed: ${error.message}`)
|
||||
}
|
||||
throw new Error('Unknown API error')
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
|
||||
const url = new URL(`${this.baseUrl}${endpoint}`)
|
||||
|
||||
if (params) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
const value = params[key]
|
||||
if (value !== undefined && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => url.searchParams.append(key, String(v)))
|
||||
} else {
|
||||
url.searchParams.append(key, String(value))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const authHeaders = this.getAuthHeaders()
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...authHeaders,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
throw new Error(`HTTP ${response.status}: ${errorData.detail || response.statusText}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async post<T>(endpoint: string, data?: any): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'POST',
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
async put<T>(endpoint: string, data?: any): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'PUT',
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
async delete<T>(endpoint: string): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const apiClient = new ApiClient()
|
||||
|
||||
// Health and status endpoints
|
||||
export const healthApi = {
|
||||
/**
|
||||
* Get system health status
|
||||
*/
|
||||
async getHealth(): Promise<HealthCheck> {
|
||||
return apiClient.get<HealthCheck>('/health')
|
||||
},
|
||||
|
||||
/**
|
||||
* Get detailed system status
|
||||
*/
|
||||
async getStatus(): Promise<SystemStatus> {
|
||||
return apiClient.get<SystemStatus>('/api/v1/overview')
|
||||
},
|
||||
}
|
||||
|
||||
export default apiClient
|
||||
47
src/services/authApi.ts
Normal file
47
src/services/authApi.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Authentication API Service
|
||||
* Handles JWT token generation and validation
|
||||
*/
|
||||
import { apiClient } from './api'
|
||||
|
||||
export interface TokenRequest {
|
||||
name: string
|
||||
list_of_resources: string[]
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
token: string
|
||||
expires_at: string
|
||||
resources: string[]
|
||||
}
|
||||
|
||||
export interface TokenValidation {
|
||||
valid: boolean
|
||||
expires_at?: string
|
||||
resources?: string[]
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
/**
|
||||
* Generate a new JWT token for the dashboard
|
||||
*/
|
||||
async generateToken(request: TokenRequest): Promise<TokenResponse> {
|
||||
return apiClient.post<TokenResponse>('/api/v1/tokens/generate', request)
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate an existing token
|
||||
*/
|
||||
async validateToken(token: string): Promise<TokenValidation> {
|
||||
return apiClient.post<TokenValidation>('/api/v1/tokens/validate', { token })
|
||||
},
|
||||
|
||||
/**
|
||||
* Revoke a token
|
||||
*/
|
||||
async revokeToken(token: string): Promise<{ message: string }> {
|
||||
return apiClient.post<{ message: string }>('/api/v1/tokens/revoke', { token })
|
||||
}
|
||||
}
|
||||
|
||||
export default authApi
|
||||
30
src/services/index.ts
Normal file
30
src/services/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* API Services Index
|
||||
* Central export point for all API services
|
||||
*/
|
||||
|
||||
// Export base API client and types
|
||||
export * from './api'
|
||||
|
||||
// Export service modules
|
||||
export { sensorsApi } from './sensorsApi'
|
||||
export { roomsApi } from './roomsApi'
|
||||
export { analyticsApi } from './analyticsApi'
|
||||
export { healthApi } from './api'
|
||||
export { authApi } from './authApi'
|
||||
|
||||
// Re-export commonly used types for convenience
|
||||
export type {
|
||||
SensorReading,
|
||||
SensorInfo,
|
||||
RoomInfo,
|
||||
RoomData,
|
||||
DataQuery,
|
||||
DataResponse,
|
||||
AnalyticsSummary,
|
||||
EnergyTrends,
|
||||
RoomComparison,
|
||||
SystemEvent,
|
||||
HealthCheck,
|
||||
SystemStatus
|
||||
} from './api'
|
||||
Reference in New Issue
Block a user