Refactor API and store logic for sensor and room management
- Add sensor name to SensorInfo interface - Update API client to conditionally include auth headers - Add saveToken endpoint to authApi - Refactor roomsApi to use getRoomNames endpoint - Change sensorsApi to use /api/v1/sensors/get - Improve token handling and JWT decoding in auth store - Refactor room loading to use API client in energy store - Add helper to transform API sensor data - Update SensorManagementView to load sensors from API and fix filtering
This commit is contained in:
@@ -76,6 +76,7 @@ export interface SensorReading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SensorInfo {
|
export interface SensorInfo {
|
||||||
|
name: string
|
||||||
sensor_id: string
|
sensor_id: string
|
||||||
sensor_type: SensorType
|
sensor_type: SensorType
|
||||||
room?: string
|
room?: string
|
||||||
@@ -211,13 +212,49 @@ class ApiClient {
|
|||||||
this.baseUrl = baseUrl
|
this.baseUrl = baseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAuthHeaders(): Record<string, string> {
|
private shouldIncludeAuth(endpoint: string): boolean {
|
||||||
|
// Endpoints that do NOT require authentication
|
||||||
|
const publicEndpoints = [
|
||||||
|
'/health',
|
||||||
|
'/api/v1/tokens/generate',
|
||||||
|
'/api/v1/tokens/validate',
|
||||||
|
'/api/v1/ingestion/',
|
||||||
|
]
|
||||||
|
|
||||||
|
// Special case: token save, revoke operations might need auth depending on backend implementation
|
||||||
|
// For now, let's include auth for them to be safe
|
||||||
|
return !publicEndpoints.some((publicPath) => endpoint.startsWith(publicPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAuthHeaders(endpoint: string): Record<string, string> {
|
||||||
|
// Only include auth headers for endpoints that require authentication
|
||||||
|
if (!this.shouldIncludeAuth(endpoint)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamically get auth headers to avoid circular imports
|
// Dynamically get auth headers to avoid circular imports
|
||||||
try {
|
try {
|
||||||
|
// Try to get from window first (for when store is exposed)
|
||||||
const authStore = (window as any).__AUTH_STORE__
|
const authStore = (window as any).__AUTH_STORE__
|
||||||
if (authStore && typeof authStore.getAuthHeader === 'function') {
|
if (authStore && typeof authStore.getAuthHeader === 'function') {
|
||||||
return authStore.getAuthHeader()
|
return authStore.getAuthHeader()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: try to access the auth store directly
|
||||||
|
// This requires the auth store to be initialized
|
||||||
|
const token = localStorage.getItem('dashboard_auth_token')
|
||||||
|
if (token) {
|
||||||
|
// Check if token is still valid
|
||||||
|
const expiry = localStorage.getItem('dashboard_token_expiry')
|
||||||
|
if (expiry) {
|
||||||
|
const expiryTime = new Date(expiry).getTime()
|
||||||
|
const currentTime = new Date().getTime()
|
||||||
|
|
||||||
|
if (currentTime < expiryTime) {
|
||||||
|
return { Authorization: `Bearer ${token}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Could not get auth headers:', error)
|
console.warn('Could not get auth headers:', error)
|
||||||
}
|
}
|
||||||
@@ -227,7 +264,7 @@ class ApiClient {
|
|||||||
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||||
const url = `${this.baseUrl}${endpoint}`
|
const url = `${this.baseUrl}${endpoint}`
|
||||||
|
|
||||||
const authHeaders = this.getAuthHeaders()
|
const authHeaders = this.getAuthHeaders(endpoint)
|
||||||
const config: RequestInit = {
|
const config: RequestInit = {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -270,7 +307,7 @@ class ApiClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeaders = this.getAuthHeaders()
|
const authHeaders = this.getAuthHeaders(endpoint)
|
||||||
const response = await fetch(url.toString(), {
|
const response = await fetch(url.toString(), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
/**
|
|
||||||
* Authentication API Service
|
|
||||||
* Handles JWT token generation and validation
|
|
||||||
*/
|
|
||||||
import { apiClient } from './api'
|
import { apiClient } from './api'
|
||||||
|
|
||||||
export interface TokenRequest {
|
export interface TokenRequest {
|
||||||
name: string
|
name: string
|
||||||
list_of_resources: string[]
|
list_of_resources: string[]
|
||||||
|
data_aggregation?: boolean
|
||||||
|
time_aggregation?: boolean
|
||||||
|
embargo?: number
|
||||||
|
exp_hours?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenResponse {
|
export interface TokenResponse {
|
||||||
token: string
|
token: string
|
||||||
expires_at: string
|
|
||||||
resources: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenValidation {
|
export interface TokenValidation {
|
||||||
@@ -26,6 +24,10 @@ export const authApi = {
|
|||||||
return apiClient.post<TokenResponse>('/api/v1/tokens/generate', request)
|
return apiClient.post<TokenResponse>('/api/v1/tokens/generate', request)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async saveToken(token: string): Promise<{ token: string; datetime: string; active: boolean }> {
|
||||||
|
return apiClient.post<{ token: string; datetime: string; active: boolean }>('/api/v1/tokens/save', { token })
|
||||||
|
},
|
||||||
|
|
||||||
async validateToken(token: string): Promise<TokenValidation> {
|
async validateToken(token: string): Promise<TokenValidation> {
|
||||||
return apiClient.post<TokenValidation>('/api/v1/tokens/validate', { token })
|
return apiClient.post<TokenValidation>('/api/v1/tokens/validate', { token })
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
/**
|
|
||||||
* Rooms API Service
|
|
||||||
* Handles room-related API calls
|
|
||||||
*/
|
|
||||||
import { apiClient, type RoomInfo, type RoomData } from './api'
|
import { apiClient, type RoomInfo, type RoomData } from './api'
|
||||||
|
|
||||||
export const roomsApi = {
|
export const roomsApi = {
|
||||||
/**
|
async getRoomNames(): Promise<{ rooms: string[] }> {
|
||||||
* Get list of all rooms with sensor counts and latest metrics
|
return apiClient.get<{ rooms: string[] }>('/api/v1/rooms/names')
|
||||||
*/
|
},
|
||||||
|
|
||||||
async getRooms(): Promise<RoomInfo[]> {
|
async getRooms(): Promise<RoomInfo[]> {
|
||||||
return apiClient.get<RoomInfo[]>('/api/v1/rooms')
|
return apiClient.get<RoomInfo[]>('/api/v1/rooms')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Get historical data for a specific room
|
|
||||||
*/
|
|
||||||
async getRoomData(
|
async getRoomData(
|
||||||
roomName: string,
|
roomName: string,
|
||||||
params?: {
|
params?: {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const sensorsApi = {
|
|||||||
sensor_type?: SensorType
|
sensor_type?: SensorType
|
||||||
status?: SensorStatus
|
status?: SensorStatus
|
||||||
}): Promise<SensorInfo[]> {
|
}): Promise<SensorInfo[]> {
|
||||||
return apiClient.get<SensorInfo[]>('/api/v1/sensors', params)
|
return apiClient.get<SensorInfo[]>('/api/v1/sensors/get', params)
|
||||||
},
|
},
|
||||||
|
|
||||||
async getSensor(sensorId: string): Promise<SensorInfo> {
|
async getSensor(sensorId: string): Promise<SensorInfo> {
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { authApi, type TokenRequest, type TokenResponse } from '@/services/authApi'
|
import { authApi, type TokenRequest } from '@/services/authApi'
|
||||||
|
|
||||||
|
// Helper function to decode JWT token payload (without verification)
|
||||||
|
function decodeJwtPayload(token: string) {
|
||||||
|
try {
|
||||||
|
const base64Url = token.split('.')[1]
|
||||||
|
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||||
|
const jsonPayload = decodeURIComponent(
|
||||||
|
atob(base64)
|
||||||
|
.split('')
|
||||||
|
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
||||||
|
.join(''),
|
||||||
|
)
|
||||||
|
return JSON.parse(jsonPayload)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to decode JWT payload:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const TOKEN_STORAGE_KEY = 'dashboard_auth_token'
|
const TOKEN_STORAGE_KEY = 'dashboard_auth_token'
|
||||||
const TOKEN_EXPIRY_KEY = 'dashboard_token_expiry'
|
const TOKEN_EXPIRY_KEY = 'dashboard_token_expiry'
|
||||||
@@ -53,30 +71,35 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
try {
|
try {
|
||||||
const request: TokenRequest = {
|
const request: TokenRequest = {
|
||||||
name,
|
name,
|
||||||
list_of_resources: [
|
list_of_resources: ['sensors', 'rooms', 'analytics', 'health', 'data', 'export', 'events'],
|
||||||
'sensors',
|
data_aggregation: true,
|
||||||
'rooms',
|
time_aggregation: true,
|
||||||
'analytics',
|
embargo: 0,
|
||||||
'health',
|
exp_hours: 24,
|
||||||
'data',
|
|
||||||
'export',
|
|
||||||
'events'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authApi.generateToken(request)
|
const response = await authApi.generateToken(request)
|
||||||
|
|
||||||
|
// Save token to backend database immediately after generation
|
||||||
|
await authApi.saveToken(response.token)
|
||||||
|
|
||||||
|
// Decode the JWT to get expiration and resources
|
||||||
|
const payload = decodeJwtPayload(response.token)
|
||||||
|
if (!payload) {
|
||||||
|
throw new Error('Failed to decode token payload')
|
||||||
|
}
|
||||||
|
|
||||||
// Store token data
|
// Store token data
|
||||||
token.value = response.token
|
token.value = response.token
|
||||||
tokenExpiry.value = response.expires_at
|
tokenExpiry.value = new Date(payload.exp * 1000).toISOString()
|
||||||
tokenResources.value = response.resources || request.list_of_resources
|
tokenResources.value = payload.list_of_resources || request.list_of_resources
|
||||||
|
|
||||||
// Persist to localStorage
|
// Persist to localStorage
|
||||||
localStorage.setItem(TOKEN_STORAGE_KEY, response.token)
|
localStorage.setItem(TOKEN_STORAGE_KEY, response.token)
|
||||||
localStorage.setItem(TOKEN_EXPIRY_KEY, response.expires_at)
|
localStorage.setItem(TOKEN_EXPIRY_KEY, tokenExpiry.value)
|
||||||
localStorage.setItem(TOKEN_RESOURCES_KEY, JSON.stringify(tokenResources.value))
|
localStorage.setItem(TOKEN_RESOURCES_KEY, JSON.stringify(tokenResources.value))
|
||||||
|
|
||||||
console.log('Authentication successful, token expires at:', response.expires_at)
|
console.log('Authentication successful, token expires at:', tokenExpiry.value)
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err instanceof Error ? err.message : 'Failed to generate token'
|
const errorMessage = err instanceof Error ? err.message : 'Failed to generate token'
|
||||||
@@ -123,7 +146,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
localStorage.removeItem(TOKEN_RESOURCES_KEY)
|
localStorage.removeItem(TOKEN_RESOURCES_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadTokenFromStorage() {
|
async function loadTokenFromStorage() {
|
||||||
const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY)
|
const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY)
|
||||||
const storedExpiry = localStorage.getItem(TOKEN_EXPIRY_KEY)
|
const storedExpiry = localStorage.getItem(TOKEN_EXPIRY_KEY)
|
||||||
const storedResources = localStorage.getItem(TOKEN_RESOURCES_KEY)
|
const storedResources = localStorage.getItem(TOKEN_RESOURCES_KEY)
|
||||||
@@ -159,7 +182,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
|
|
||||||
async function ensureAuthenticated(): Promise<boolean> {
|
async function ensureAuthenticated(): Promise<boolean> {
|
||||||
// Try to load from storage first
|
// Try to load from storage first
|
||||||
if (loadTokenFromStorage() && isAuthenticated.value) {
|
if ((await loadTokenFromStorage()) && isAuthenticated.value) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +198,10 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize on store creation
|
// Initialize on store creation (async)
|
||||||
loadTokenFromStorage()
|
loadTokenFromStorage().catch(error => {
|
||||||
|
console.warn('Failed to load token from storage:', error)
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
@@ -196,6 +221,6 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
clearToken,
|
clearToken,
|
||||||
loadTokenFromStorage,
|
loadTokenFromStorage,
|
||||||
ensureAuthenticated,
|
ensureAuthenticated,
|
||||||
getAuthHeader
|
getAuthHeader,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -207,7 +207,10 @@ export const useEnergyStore = defineStore('energy', () => {
|
|||||||
const data = newDataBuffer.shift() // Get the oldest data point
|
const data = newDataBuffer.shift() // Get the oldest data point
|
||||||
if (data) {
|
if (data) {
|
||||||
// Skip non-data messages (connection establishment, proxy info, etc.)
|
// Skip non-data messages (connection establishment, proxy info, etc.)
|
||||||
if (data.type && (data.type === 'connection_established' || data.type === 'proxy_info')) {
|
if (
|
||||||
|
'type' in data &&
|
||||||
|
(data.type === 'connection_established' || data.type === 'proxy_info')
|
||||||
|
) {
|
||||||
console.log('Received system message:', data.type)
|
console.log('Received system message:', data.type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -339,6 +342,331 @@ export const useEnergyStore = defineStore('energy', () => {
|
|||||||
return 'critical'
|
return 'critical'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sensor management functions
|
||||||
|
function updateSensorRoom(sensorId: string, newRoom: string) {
|
||||||
|
const sensor = sensorDevices.get(sensorId)
|
||||||
|
if (sensor) {
|
||||||
|
sensor.room = newRoom
|
||||||
|
sensorDevices.set(sensorId, { ...sensor })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeSensorAction(sensorId: string, actionId: string, parameters?: any) {
|
||||||
|
const sensor = sensorDevices.get(sensorId)
|
||||||
|
if (!sensor) return false
|
||||||
|
|
||||||
|
const action = sensor.capabilities.actions.find((a) => a.id === actionId)
|
||||||
|
if (!action) return false
|
||||||
|
|
||||||
|
// Simulate API call to device
|
||||||
|
console.log(`Executing action ${actionId} on sensor ${sensorId}`, parameters)
|
||||||
|
|
||||||
|
// Here you would make the actual API call to control the device
|
||||||
|
// For now, we'll simulate a successful action
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(`Action ${action.name} executed successfully on ${sensor.name}`)
|
||||||
|
resolve(true)
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSensorsByRoom(room: string): SensorDevice[] {
|
||||||
|
return Array.from(sensorDevices.values()).filter((sensor) => sensor.room === room)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSensorsByType(type: SensorDevice['type']): SensorDevice[] {
|
||||||
|
return Array.from(sensorDevices.values()).filter((sensor) => sensor.type === type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Room management functions
|
||||||
|
const loadRoomsFromAPI = async (): Promise<void> => {
|
||||||
|
if (roomsLoading.value || roomsLoaded.value) {
|
||||||
|
return // Already loading or loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
roomsLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use the API client which handles authentication properly
|
||||||
|
const data = await roomsApi.getRoomNames()
|
||||||
|
if (data.rooms && Array.isArray(data.rooms)) {
|
||||||
|
availableRooms.value = data.rooms.sort()
|
||||||
|
roomsLoaded.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no rooms found, use empty list
|
||||||
|
console.warn('No rooms found in API response, starting with empty list')
|
||||||
|
availableRooms.value = []
|
||||||
|
roomsLoaded.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading rooms:', error)
|
||||||
|
// Start with empty list on error
|
||||||
|
availableRooms.value = []
|
||||||
|
roomsLoaded.value = true
|
||||||
|
} finally {
|
||||||
|
roomsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRoom(roomName: string): boolean {
|
||||||
|
if (!roomName.trim()) return false
|
||||||
|
|
||||||
|
// Check if room already exists
|
||||||
|
if (availableRooms.value.includes(roomName.trim())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add room to available rooms list
|
||||||
|
availableRooms.value.push(roomName.trim())
|
||||||
|
availableRooms.value.sort() // Keep rooms sorted alphabetically
|
||||||
|
|
||||||
|
console.log(`Added new room: ${roomName}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeRoom(roomName: string): boolean {
|
||||||
|
const index = availableRooms.value.indexOf(roomName)
|
||||||
|
if (index === -1) return false
|
||||||
|
|
||||||
|
// Check if any sensors are assigned to this room
|
||||||
|
const sensorsInRoom = Array.from(sensorDevices.values()).filter(
|
||||||
|
(sensor) => sensor.room === roomName,
|
||||||
|
)
|
||||||
|
if (sensorsInRoom.length > 0) {
|
||||||
|
// Reassign sensors to 'Unassigned'
|
||||||
|
sensorsInRoom.forEach((sensor) => {
|
||||||
|
sensor.room = ''
|
||||||
|
sensorDevices.set(sensor.id, { ...sensor })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove room data
|
||||||
|
roomsData.delete(roomName)
|
||||||
|
|
||||||
|
// Remove from available rooms
|
||||||
|
availableRooms.value.splice(index, 1)
|
||||||
|
|
||||||
|
console.log(`Removed room: ${roomName}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoomStats(roomName: string) {
|
||||||
|
const sensorsInRoom = getSensorsByRoom(roomName)
|
||||||
|
const roomMetrics = roomsData.get(roomName)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sensorCount: sensorsInRoom.length,
|
||||||
|
sensorTypes: [...new Set(sensorsInRoom.map((s) => s.type))],
|
||||||
|
hasMetrics: !!roomMetrics,
|
||||||
|
energyConsumption: roomMetrics?.energy.current || 0,
|
||||||
|
co2Level: roomMetrics?.co2.current || 0,
|
||||||
|
lastUpdated: roomMetrics?.lastUpdated || null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllRoomsWithStats() {
|
||||||
|
return availableRooms.value.map((room) => ({
|
||||||
|
name: room,
|
||||||
|
...getRoomStats(room),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Integration Functions
|
||||||
|
async function handleApiCall<T>(apiCall: () => Promise<T>): Promise<T | null> {
|
||||||
|
apiLoading.value = true
|
||||||
|
apiError.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await apiCall()
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||||
|
|
||||||
|
if (errorMessage.includes('401') || errorMessage.includes('Authorization')) {
|
||||||
|
console.warn('Authentication error detected, attempting to re-authenticate...')
|
||||||
|
|
||||||
|
// Try to get fresh auth token
|
||||||
|
try {
|
||||||
|
const authStore = (window as any).__AUTH_STORE__
|
||||||
|
if (authStore && typeof authStore.ensureAuthenticated === 'function') {
|
||||||
|
const authSuccess = await authStore.ensureAuthenticated()
|
||||||
|
if (authSuccess) {
|
||||||
|
console.log('Re-authentication successful, retrying API call...')
|
||||||
|
// Retry the original API call
|
||||||
|
try {
|
||||||
|
const retryResult = await apiCall()
|
||||||
|
return retryResult
|
||||||
|
} catch (retryError) {
|
||||||
|
const retryErrorMessage =
|
||||||
|
retryError instanceof Error ? retryError.message : 'Retry failed'
|
||||||
|
apiError.value = retryErrorMessage
|
||||||
|
console.error('API retry failed:', retryErrorMessage)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (authError) {
|
||||||
|
console.error('Re-authentication failed:', authError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiError.value = errorMessage
|
||||||
|
console.error('API call failed:', errorMessage)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
apiLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to transform API sensor to expected format
|
||||||
|
function transformApiSensor(apiSensor: any) {
|
||||||
|
return {
|
||||||
|
id: apiSensor.sensor_id,
|
||||||
|
sensor_id: apiSensor.sensor_id,
|
||||||
|
name: apiSensor.name || apiSensor.sensor_id,
|
||||||
|
type: apiSensor.sensor_type,
|
||||||
|
sensor_type: apiSensor.sensor_type,
|
||||||
|
room: apiSensor.room,
|
||||||
|
status: apiSensor.status === 'active' ? 'online' : apiSensor.status,
|
||||||
|
location: apiSensor.location,
|
||||||
|
// Add capabilities based on sensor type
|
||||||
|
capabilities: {
|
||||||
|
monitoring: [apiSensor.sensor_type],
|
||||||
|
actions: [], // API sensors don't have actions yet
|
||||||
|
},
|
||||||
|
// Add metadata
|
||||||
|
metadata: {
|
||||||
|
created_at: apiSensor.created_at,
|
||||||
|
updated_at: apiSensor.updated_at,
|
||||||
|
manufacturer: apiSensor.manufacturer,
|
||||||
|
model: apiSensor.model,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sensors API functions
|
||||||
|
async function fetchApiSensors(params?: { room?: string; sensor_type?: any; status?: any }) {
|
||||||
|
const result = await handleApiCall(() => sensorsApi.getSensors(params))
|
||||||
|
if (result.sensors) {
|
||||||
|
result.sensors.forEach((sensor) => {
|
||||||
|
sensorDevices.set(sensor.id, sensor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchApiSensorData(
|
||||||
|
sensorId: string,
|
||||||
|
params?: { start_time?: number; end_time?: number; limit?: number; offset?: number },
|
||||||
|
) {
|
||||||
|
return handleApiCall(() => sensorsApi.getSensorData(sensorId, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateApiSensorMetadata(sensorId: string, metadata: Record<string, any>) {
|
||||||
|
return handleApiCall(() => sensorsApi.updateSensorMetadata(sensorId, metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteApiSensor(sensorId: string) {
|
||||||
|
return handleApiCall(() => sensorsApi.deleteSensor(sensorId))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportApiData(params: {
|
||||||
|
start_time: number
|
||||||
|
end_time: number
|
||||||
|
sensor_ids?: string
|
||||||
|
format?: 'json' | 'csv'
|
||||||
|
}) {
|
||||||
|
return handleApiCall(() => sensorsApi.exportData(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rooms API functions
|
||||||
|
async function fetchApiRooms() {
|
||||||
|
const result = await handleApiCall(() => roomsApi.getRooms())
|
||||||
|
if (result) {
|
||||||
|
apiRooms.value = result
|
||||||
|
// Update available rooms from API data
|
||||||
|
const roomNames = result.map((room) => room.room).filter((name) => name)
|
||||||
|
if (roomNames.length > 0) {
|
||||||
|
availableRooms.value = [...new Set([...availableRooms.value, ...roomNames])].sort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchApiRoomData(
|
||||||
|
roomName: string,
|
||||||
|
params?: { start_time?: number; end_time?: number; limit?: number },
|
||||||
|
) {
|
||||||
|
return handleApiCall(() => roomsApi.getRoomData(roomName, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analytics API functions
|
||||||
|
async function fetchAnalyticsSummary(hours: number = 24) {
|
||||||
|
const result = await handleApiCall(() => analyticsApi.getAnalyticsSummary(hours))
|
||||||
|
if (result) {
|
||||||
|
analyticsData.value.summary = result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchEnergyTrends(hours: number = 168) {
|
||||||
|
const result = await handleApiCall(() => analyticsApi.getEnergyTrends(hours))
|
||||||
|
if (result) {
|
||||||
|
analyticsData.value.trends = result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRoomComparison(hours: number = 24) {
|
||||||
|
const result = await handleApiCall(() => analyticsApi.getRoomComparison(hours))
|
||||||
|
if (result) {
|
||||||
|
analyticsData.value.roomComparison = result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSystemEvents(params?: {
|
||||||
|
severity?: string
|
||||||
|
event_type?: string
|
||||||
|
hours?: number
|
||||||
|
limit?: number
|
||||||
|
}) {
|
||||||
|
return handleApiCall(() => analyticsApi.getEvents(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health API functions
|
||||||
|
async function fetchSystemStatus() {
|
||||||
|
const result = await handleApiCall(() => healthApi.getStatus())
|
||||||
|
if (result) {
|
||||||
|
systemStatus.value = result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchHealthStatus() {
|
||||||
|
const result = await handleApiCall(() => healthApi.getHealth())
|
||||||
|
if (result) {
|
||||||
|
healthStatus.value = result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize data from APIs
|
||||||
|
async function initializeFromApi() {
|
||||||
|
await Promise.allSettled([
|
||||||
|
loadRoomsFromAPI(), // Load room names first
|
||||||
|
fetchApiSensors(),
|
||||||
|
fetchApiRooms(),
|
||||||
|
fetchAnalyticsSummary(),
|
||||||
|
fetchSystemStatus(),
|
||||||
|
fetchHealthStatus(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize mock sensors on store creation
|
||||||
|
|
||||||
// Initialize mock sensor devices
|
// Initialize mock sensor devices
|
||||||
function initializeMockSensors() {
|
function initializeMockSensors() {
|
||||||
const mockSensors: SensorDevice[] = [
|
const mockSensors: SensorDevice[] = [
|
||||||
@@ -470,322 +798,7 @@ export const useEnergyStore = defineStore('energy', () => {
|
|||||||
sensorDevices.set(sensor.id, sensor)
|
sensorDevices.set(sensor.id, sensor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
//initializeMockSensors()
|
||||||
// Sensor management functions
|
|
||||||
function updateSensorRoom(sensorId: string, newRoom: string) {
|
|
||||||
const sensor = sensorDevices.get(sensorId)
|
|
||||||
if (sensor) {
|
|
||||||
sensor.room = newRoom
|
|
||||||
sensorDevices.set(sensorId, { ...sensor })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeSensorAction(sensorId: string, actionId: string, parameters?: any) {
|
|
||||||
const sensor = sensorDevices.get(sensorId)
|
|
||||||
if (!sensor) return false
|
|
||||||
|
|
||||||
const action = sensor.capabilities.actions.find((a) => a.id === actionId)
|
|
||||||
if (!action) return false
|
|
||||||
|
|
||||||
// Simulate API call to device
|
|
||||||
console.log(`Executing action ${actionId} on sensor ${sensorId}`, parameters)
|
|
||||||
|
|
||||||
// Here you would make the actual API call to control the device
|
|
||||||
// For now, we'll simulate a successful action
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log(`Action ${action.name} executed successfully on ${sensor.name}`)
|
|
||||||
resolve(true)
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSensorsByRoom(room: string): SensorDevice[] {
|
|
||||||
return Array.from(sensorDevices.values()).filter((sensor) => sensor.room === room)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSensorsByType(type: SensorDevice['type']): SensorDevice[] {
|
|
||||||
return Array.from(sensorDevices.values()).filter((sensor) => sensor.type === type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Room management functions
|
|
||||||
const loadRoomsFromAPI = async (): Promise<void> => {
|
|
||||||
if (roomsLoading.value || roomsLoaded.value) {
|
|
||||||
return // Already loading or loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
roomsLoading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to load from microservices API first
|
|
||||||
const response = await fetch('/api/v1/rooms/names')
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json()
|
|
||||||
if (data.rooms && Array.isArray(data.rooms)) {
|
|
||||||
availableRooms.value = data.rooms.sort()
|
|
||||||
roomsLoaded.value = true
|
|
||||||
console.log('Loaded rooms from microservices API:', data.rooms.length)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: try direct sensor service connection
|
|
||||||
const directResponse = await fetch('http://localhost:8007/rooms/names')
|
|
||||||
|
|
||||||
if (directResponse.ok) {
|
|
||||||
const data = await directResponse.json()
|
|
||||||
if (data.rooms && Array.isArray(data.rooms)) {
|
|
||||||
availableRooms.value = data.rooms.sort()
|
|
||||||
roomsLoaded.value = true
|
|
||||||
console.log('Loaded rooms from sensor service:', data.rooms.length)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both fail, use empty list and log warning
|
|
||||||
console.warn('Failed to load rooms from API, starting with empty list')
|
|
||||||
availableRooms.value = []
|
|
||||||
roomsLoaded.value = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading rooms:', error)
|
|
||||||
// Start with empty list on error
|
|
||||||
availableRooms.value = []
|
|
||||||
roomsLoaded.value = true
|
|
||||||
} finally {
|
|
||||||
roomsLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRoom(roomName: string): boolean {
|
|
||||||
if (!roomName.trim()) return false
|
|
||||||
|
|
||||||
// Check if room already exists
|
|
||||||
if (availableRooms.value.includes(roomName.trim())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add room to available rooms list
|
|
||||||
availableRooms.value.push(roomName.trim())
|
|
||||||
availableRooms.value.sort() // Keep rooms sorted alphabetically
|
|
||||||
|
|
||||||
console.log(`Added new room: ${roomName}`)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRoom(roomName: string): boolean {
|
|
||||||
const index = availableRooms.value.indexOf(roomName)
|
|
||||||
if (index === -1) return false
|
|
||||||
|
|
||||||
// Check if any sensors are assigned to this room
|
|
||||||
const sensorsInRoom = Array.from(sensorDevices.values()).filter(
|
|
||||||
(sensor) => sensor.room === roomName,
|
|
||||||
)
|
|
||||||
if (sensorsInRoom.length > 0) {
|
|
||||||
// Reassign sensors to 'Unassigned'
|
|
||||||
sensorsInRoom.forEach((sensor) => {
|
|
||||||
sensor.room = ''
|
|
||||||
sensorDevices.set(sensor.id, { ...sensor })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove room data
|
|
||||||
roomsData.delete(roomName)
|
|
||||||
|
|
||||||
// Remove from available rooms
|
|
||||||
availableRooms.value.splice(index, 1)
|
|
||||||
|
|
||||||
console.log(`Removed room: ${roomName}`)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRoomStats(roomName: string) {
|
|
||||||
const sensorsInRoom = getSensorsByRoom(roomName)
|
|
||||||
const roomMetrics = roomsData.get(roomName)
|
|
||||||
|
|
||||||
return {
|
|
||||||
sensorCount: sensorsInRoom.length,
|
|
||||||
sensorTypes: [...new Set(sensorsInRoom.map((s) => s.type))],
|
|
||||||
hasMetrics: !!roomMetrics,
|
|
||||||
energyConsumption: roomMetrics?.energy.current || 0,
|
|
||||||
co2Level: roomMetrics?.co2.current || 0,
|
|
||||||
lastUpdated: roomMetrics?.lastUpdated || null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllRoomsWithStats() {
|
|
||||||
return availableRooms.value.map((room) => ({
|
|
||||||
name: room,
|
|
||||||
...getRoomStats(room),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// API Integration Functions
|
|
||||||
async function handleApiCall<T>(apiCall: () => Promise<T>): Promise<T | null> {
|
|
||||||
apiLoading.value = true
|
|
||||||
apiError.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await apiCall()
|
|
||||||
return result
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
|
||||||
|
|
||||||
if (errorMessage.includes('401') || errorMessage.includes('Authorization')) {
|
|
||||||
console.warn('Authentication error detected, attempting to re-authenticate...')
|
|
||||||
|
|
||||||
// Try to get fresh auth token
|
|
||||||
try {
|
|
||||||
const authStore = (window as any).__AUTH_STORE__
|
|
||||||
if (authStore && typeof authStore.ensureAuthenticated === 'function') {
|
|
||||||
const authSuccess = await authStore.ensureAuthenticated()
|
|
||||||
if (authSuccess) {
|
|
||||||
console.log('Re-authentication successful, retrying API call...')
|
|
||||||
// Retry the original API call
|
|
||||||
try {
|
|
||||||
const retryResult = await apiCall()
|
|
||||||
return retryResult
|
|
||||||
} catch (retryError) {
|
|
||||||
const retryErrorMessage =
|
|
||||||
retryError instanceof Error ? retryError.message : 'Retry failed'
|
|
||||||
apiError.value = retryErrorMessage
|
|
||||||
console.error('API retry failed:', retryErrorMessage)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (authError) {
|
|
||||||
console.error('Re-authentication failed:', authError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apiError.value = errorMessage
|
|
||||||
console.error('API call failed:', errorMessage)
|
|
||||||
return null
|
|
||||||
} finally {
|
|
||||||
apiLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sensors API functions
|
|
||||||
async function fetchApiSensors(params?: { room?: string; sensor_type?: any; status?: any }) {
|
|
||||||
const result = await handleApiCall(() => sensorsApi.getSensors(params))
|
|
||||||
if (result) {
|
|
||||||
apiSensors.value = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchApiSensorData(
|
|
||||||
sensorId: string,
|
|
||||||
params?: { start_time?: number; end_time?: number; limit?: number; offset?: number },
|
|
||||||
) {
|
|
||||||
return handleApiCall(() => sensorsApi.getSensorData(sensorId, params))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateApiSensorMetadata(sensorId: string, metadata: Record<string, any>) {
|
|
||||||
return handleApiCall(() => sensorsApi.updateSensorMetadata(sensorId, metadata))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteApiSensor(sensorId: string) {
|
|
||||||
return handleApiCall(() => sensorsApi.deleteSensor(sensorId))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exportApiData(params: {
|
|
||||||
start_time: number
|
|
||||||
end_time: number
|
|
||||||
sensor_ids?: string
|
|
||||||
format?: 'json' | 'csv'
|
|
||||||
}) {
|
|
||||||
return handleApiCall(() => sensorsApi.exportData(params))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rooms API functions
|
|
||||||
async function fetchApiRooms() {
|
|
||||||
const result = await handleApiCall(() => roomsApi.getRooms())
|
|
||||||
if (result) {
|
|
||||||
apiRooms.value = result
|
|
||||||
// Update available rooms from API data
|
|
||||||
const roomNames = result.map((room) => room.room).filter((name) => name)
|
|
||||||
if (roomNames.length > 0) {
|
|
||||||
availableRooms.value = [...new Set([...availableRooms.value, ...roomNames])].sort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchApiRoomData(
|
|
||||||
roomName: string,
|
|
||||||
params?: { start_time?: number; end_time?: number; limit?: number },
|
|
||||||
) {
|
|
||||||
return handleApiCall(() => roomsApi.getRoomData(roomName, params))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Analytics API functions
|
|
||||||
async function fetchAnalyticsSummary(hours: number = 24) {
|
|
||||||
const result = await handleApiCall(() => analyticsApi.getAnalyticsSummary(hours))
|
|
||||||
if (result) {
|
|
||||||
analyticsData.value.summary = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchEnergyTrends(hours: number = 168) {
|
|
||||||
const result = await handleApiCall(() => analyticsApi.getEnergyTrends(hours))
|
|
||||||
if (result) {
|
|
||||||
analyticsData.value.trends = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchRoomComparison(hours: number = 24) {
|
|
||||||
const result = await handleApiCall(() => analyticsApi.getRoomComparison(hours))
|
|
||||||
if (result) {
|
|
||||||
analyticsData.value.roomComparison = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchSystemEvents(params?: {
|
|
||||||
severity?: string
|
|
||||||
event_type?: string
|
|
||||||
hours?: number
|
|
||||||
limit?: number
|
|
||||||
}) {
|
|
||||||
return handleApiCall(() => analyticsApi.getEvents(params))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health API functions
|
|
||||||
async function fetchSystemStatus() {
|
|
||||||
const result = await handleApiCall(() => healthApi.getStatus())
|
|
||||||
if (result) {
|
|
||||||
systemStatus.value = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchHealthStatus() {
|
|
||||||
const result = await handleApiCall(() => healthApi.getHealth())
|
|
||||||
if (result) {
|
|
||||||
healthStatus.value = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize data from APIs
|
|
||||||
async function initializeFromApi() {
|
|
||||||
await Promise.allSettled([
|
|
||||||
loadRoomsFromAPI(), // Load room names first
|
|
||||||
fetchApiSensors(),
|
|
||||||
fetchApiRooms(),
|
|
||||||
fetchAnalyticsSummary(),
|
|
||||||
fetchSystemStatus(),
|
|
||||||
fetchHealthStatus(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize mock sensors on store creation
|
|
||||||
initializeMockSensors()
|
|
||||||
|
|
||||||
// Load rooms from API on store initialization
|
// Load rooms from API on store initialization
|
||||||
loadRoomsFromAPI()
|
loadRoomsFromAPI()
|
||||||
|
|||||||
@@ -24,15 +24,6 @@
|
|||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="flex flex-col sm:flex-row gap-4 flex-1">
|
<div class="flex flex-col sm:flex-row gap-4 flex-1">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<select
|
|
||||||
v-model="selectedRoom"
|
|
||||||
class="px-4 py-2 border border-gray-200 rounded-lg bg-white flex-1"
|
|
||||||
>
|
|
||||||
<option value="">All Rooms</option>
|
|
||||||
<option v-for="room in energyStore.availableRooms" :key="room" :value="room">
|
|
||||||
{{ room }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<button
|
<button
|
||||||
@click="showRoomManagementModal = true"
|
@click="showRoomManagementModal = true"
|
||||||
class="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-1"
|
class="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-1"
|
||||||
@@ -48,6 +39,15 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="hidden sm:inline">Rooms</span>
|
<span class="hidden sm:inline">Rooms</span>
|
||||||
</button>
|
</button>
|
||||||
|
<select
|
||||||
|
v-model="selectedRoom"
|
||||||
|
class="px-4 py-2 border border-gray-200 rounded-lg bg-white flex-1"
|
||||||
|
>
|
||||||
|
<option value="">All Rooms</option>
|
||||||
|
<option v-for="room in energyStore.availableRooms" :key="room" :value="room">
|
||||||
|
{{ room }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select v-model="selectedType" class="px-4 py-2 border border-gray-200 rounded-lg bg-white">
|
<select v-model="selectedType" class="px-4 py-2 border border-gray-200 rounded-lg bg-white">
|
||||||
@@ -241,13 +241,14 @@ const isExecutingAction = ref(false)
|
|||||||
const showRoomManagementModal = ref(false)
|
const showRoomManagementModal = ref(false)
|
||||||
|
|
||||||
const sensorList = computed(() => {
|
const sensorList = computed(() => {
|
||||||
|
console.log(energyStore.sensorDevices)
|
||||||
return Array.from(energyStore.sensorDevices.values()).sort((a, b) => a.name.localeCompare(b.name))
|
return Array.from(energyStore.sensorDevices.values()).sort((a, b) => a.name.localeCompare(b.name))
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredSensors = computed(() => {
|
const filteredSensors = computed(() => {
|
||||||
return sensorList.value.filter((sensor) => {
|
return sensorList.value.filter((sensor) => {
|
||||||
const matchesRoom = !selectedRoom.value || sensor.room === selectedRoom.value
|
const matchesRoom = !selectedRoom.value || sensor.room === selectedRoom.value
|
||||||
const matchesType = !selectedType.value || sensor.type === selectedType.value
|
const matchesType = !selectedType.value || sensor.sensor_type === selectedType.value
|
||||||
const matchesStatus = !selectedStatus.value || sensor.status === selectedStatus.value
|
const matchesStatus = !selectedStatus.value || sensor.status === selectedStatus.value
|
||||||
|
|
||||||
return matchesRoom && matchesType && matchesStatus
|
return matchesRoom && matchesType && matchesStatus
|
||||||
@@ -362,8 +363,12 @@ const getOccupancyColor = (occupancy: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocket connection for real-time updates
|
// Load sensors from API and connect WebSocket for real-time updates
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
// First load sensors from database
|
||||||
|
await energyStore.fetchApiSensors()
|
||||||
|
|
||||||
|
// Then connect WebSocket for real-time updates
|
||||||
if (!energyStore.isConnected) {
|
if (!energyStore.isConnected) {
|
||||||
energyStore.connect('ws://localhost:8000/ws')
|
energyStore.connect('ws://localhost:8000/ws')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user