diff --git a/src/components/common/AuthStatus.vue b/src/components/common/AuthStatus.vue new file mode 100644 index 0000000..016ff3b --- /dev/null +++ b/src/components/common/AuthStatus.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/src/composables/useApi.ts b/src/composables/useApi.ts new file mode 100644 index 0000000..80ad628 --- /dev/null +++ b/src/composables/useApi.ts @@ -0,0 +1,341 @@ +/** + * Vue Composable for API Integration + * Provides reactive API state management + */ +import { ref, reactive } from 'vue' +import { + sensorsApi, + roomsApi, + analyticsApi, + healthApi, + type SensorInfo, + type RoomInfo, + type RoomData, + type AnalyticsSummary, + type EnergyTrends, + type RoomComparison, + type SystemEvent, + type HealthCheck, + type SystemStatus, + type DataQuery, + type DataResponse +} from '@/services' + +interface ApiState { + loading: boolean + error: string | null +} + +export function useApi() { + // Global API state + const globalState = reactive({ + loading: false, + error: null + }) + + // Helper to handle API calls with state management + async function handleApiCall( + apiCall: () => Promise, + localState?: { loading: boolean; error: string | null } + ): Promise { + const state = localState || globalState + + state.loading = true + state.error = null + + try { + const result = await apiCall() + return result + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' + state.error = errorMessage + console.error('API call failed:', errorMessage) + return null + } finally { + state.loading = false + } + } + + return { + globalState, + handleApiCall + } +} + +// Sensors API composable +export function useSensorsApi() { + const state = reactive({ + loading: false, + error: null + }) + + const sensors = ref([]) + const currentSensor = ref(null) + const sensorData = ref(null) + + const { handleApiCall } = useApi() + + const fetchSensors = async (params?: { + room?: string + sensor_type?: any + status?: any + }) => { + const result = await handleApiCall( + () => sensorsApi.getSensors(params), + state + ) + if (result) { + sensors.value = result + } + return result + } + + const fetchSensor = async (sensorId: string) => { + const result = await handleApiCall( + () => sensorsApi.getSensor(sensorId), + state + ) + if (result) { + currentSensor.value = result + } + return result + } + + const fetchSensorData = async ( + sensorId: string, + params?: { + start_time?: number + end_time?: number + limit?: number + offset?: number + } + ) => { + const result = await handleApiCall( + () => sensorsApi.getSensorData(sensorId, params), + state + ) + if (result) { + sensorData.value = result + } + return result + } + + const queryData = async (query: DataQuery) => { + return handleApiCall( + () => sensorsApi.queryData(query), + state + ) + } + + const updateSensorMetadata = async ( + sensorId: string, + metadata: Record + ) => { + return handleApiCall( + () => sensorsApi.updateSensorMetadata(sensorId, metadata), + state + ) + } + + const deleteSensor = async (sensorId: string) => { + return handleApiCall( + () => sensorsApi.deleteSensor(sensorId), + state + ) + } + + const exportData = async (params: { + start_time: number + end_time: number + sensor_ids?: string + format?: 'json' | 'csv' + }) => { + return handleApiCall( + () => sensorsApi.exportData(params), + state + ) + } + + return { + state, + sensors, + currentSensor, + sensorData, + fetchSensors, + fetchSensor, + fetchSensorData, + queryData, + updateSensorMetadata, + deleteSensor, + exportData + } +} + +// Rooms API composable +export function useRoomsApi() { + const state = reactive({ + loading: false, + error: null + }) + + const rooms = ref([]) + const currentRoomData = ref(null) + + const { handleApiCall } = useApi() + + const fetchRooms = async () => { + const result = await handleApiCall( + () => roomsApi.getRooms(), + state + ) + if (result) { + rooms.value = result + } + return result + } + + const fetchRoomData = async ( + roomName: string, + params?: { + start_time?: number + end_time?: number + limit?: number + } + ) => { + const result = await handleApiCall( + () => roomsApi.getRoomData(roomName, params), + state + ) + if (result) { + currentRoomData.value = result + } + return result + } + + return { + state, + rooms, + currentRoomData, + fetchRooms, + fetchRoomData + } +} + +// Analytics API composable +export function useAnalyticsApi() { + const state = reactive({ + loading: false, + error: null + }) + + const summary = ref(null) + const trends = ref(null) + const roomComparison = ref(null) + const events = ref([]) + + const { handleApiCall } = useApi() + + const fetchAnalyticsSummary = async (hours: number = 24) => { + const result = await handleApiCall( + () => analyticsApi.getAnalyticsSummary(hours), + state + ) + if (result) { + summary.value = result + } + return result + } + + const fetchEnergyTrends = async (hours: number = 168) => { + const result = await handleApiCall( + () => analyticsApi.getEnergyTrends(hours), + state + ) + if (result) { + trends.value = result + } + return result + } + + const fetchRoomComparison = async (hours: number = 24) => { + const result = await handleApiCall( + () => analyticsApi.getRoomComparison(hours), + state + ) + if (result) { + roomComparison.value = result + } + return result + } + + const fetchEvents = async (params?: { + severity?: string + event_type?: string + hours?: number + limit?: number + }) => { + const result = await handleApiCall( + () => analyticsApi.getEvents(params), + state + ) + if (result) { + events.value = result.events + } + return result + } + + return { + state, + summary, + trends, + roomComparison, + events, + fetchAnalyticsSummary, + fetchEnergyTrends, + fetchRoomComparison, + fetchEvents + } +} + +// Health API composable +export function useHealthApi() { + const state = reactive({ + loading: false, + error: null + }) + + const health = ref(null) + const status = ref(null) + + const { handleApiCall } = useApi() + + const fetchHealth = async () => { + const result = await handleApiCall( + () => healthApi.getHealth(), + state + ) + if (result) { + health.value = result + } + return result + } + + const fetchStatus = async () => { + const result = await handleApiCall( + () => healthApi.getStatus(), + state + ) + if (result) { + status.value = result + } + return result + } + + return { + state, + health, + status, + fetchHealth, + fetchStatus + } +} \ No newline at end of file diff --git a/src/services/analyticsApi.ts b/src/services/analyticsApi.ts new file mode 100644 index 0000000..7931a17 --- /dev/null +++ b/src/services/analyticsApi.ts @@ -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 { + return apiClient.get('/api/v1/analytics/summary', { hours }) + }, + + /** + * Get energy consumption trends + */ + async getEnergyTrends(hours: number = 168): Promise { + return apiClient.get('/api/v1/analytics/trends', { hours }) + }, + + /** + * Get room-by-room comparison analytics + */ + async getRoomComparison(hours: number = 24): Promise { + return apiClient.get('/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 diff --git a/src/services/api.ts b/src/services/api.ts new file mode 100644 index 0000000..1094753 --- /dev/null +++ b/src/services/api.ts @@ -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 { + 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 +} + +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 + metadata?: Record +} + +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 + 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 { + // 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(endpoint: string, options: RequestInit = {}): Promise { + 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(endpoint: string, params?: Record): Promise { + 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(endpoint: string, data?: any): Promise { + return this.request(endpoint, { + method: 'POST', + body: data ? JSON.stringify(data) : undefined, + }) + } + + async put(endpoint: string, data?: any): Promise { + return this.request(endpoint, { + method: 'PUT', + body: data ? JSON.stringify(data) : undefined, + }) + } + + async delete(endpoint: string): Promise { + return this.request(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 { + return apiClient.get('/health') + }, + + /** + * Get detailed system status + */ + async getStatus(): Promise { + return apiClient.get('/api/v1/overview') + }, +} + +export default apiClient diff --git a/src/services/authApi.ts b/src/services/authApi.ts new file mode 100644 index 0000000..341e7e3 --- /dev/null +++ b/src/services/authApi.ts @@ -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 { + return apiClient.post('/api/v1/tokens/generate', request) + }, + + /** + * Validate an existing token + */ + async validateToken(token: string): Promise { + return apiClient.post('/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 \ No newline at end of file diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..e1180ca --- /dev/null +++ b/src/services/index.ts @@ -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' \ No newline at end of file diff --git a/src/stores/auth.ts b/src/stores/auth.ts new file mode 100644 index 0000000..0810bfb --- /dev/null +++ b/src/stores/auth.ts @@ -0,0 +1,201 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { authApi, type TokenRequest, type TokenResponse } from '@/services/authApi' + +const TOKEN_STORAGE_KEY = 'dashboard_auth_token' +const TOKEN_EXPIRY_KEY = 'dashboard_token_expiry' +const TOKEN_RESOURCES_KEY = 'dashboard_token_resources' + +export const useAuthStore = defineStore('auth', () => { + // State + const token = ref(null) + const tokenExpiry = ref(null) + const tokenResources = ref([]) + const isLoading = ref(false) + const error = ref(null) + + // Computed + const isAuthenticated = computed(() => { + if (!token.value || !tokenExpiry.value) return false + + // Check if token is expired + const expiryTime = new Date(tokenExpiry.value).getTime() + const currentTime = new Date().getTime() + + if (currentTime >= expiryTime) { + clearToken() + return false + } + + return true + }) + + const timeUntilExpiry = computed(() => { + if (!tokenExpiry.value) return null + + const expiryTime = new Date(tokenExpiry.value).getTime() + const currentTime = new Date().getTime() + const diff = expiryTime - currentTime + + if (diff <= 0) return null + + const hours = Math.floor(diff / (1000 * 60 * 60)) + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) + + return { hours, minutes, milliseconds: diff } + }) + + // Actions + async function generateToken(name: string = 'dashboard_user'): Promise { + isLoading.value = true + error.value = null + + try { + const request: TokenRequest = { + name, + list_of_resources: [ + 'sensors', + 'rooms', + 'analytics', + 'health', + 'data', + 'export', + 'events' + ] + } + + const response = await authApi.generateToken(request) + + // Store token data + token.value = response.token + tokenExpiry.value = response.expires_at + tokenResources.value = response.resources || request.list_of_resources + + // Persist to localStorage + localStorage.setItem(TOKEN_STORAGE_KEY, response.token) + localStorage.setItem(TOKEN_EXPIRY_KEY, response.expires_at) + localStorage.setItem(TOKEN_RESOURCES_KEY, JSON.stringify(tokenResources.value)) + + console.log('Authentication successful, token expires at:', response.expires_at) + return true + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to generate token' + error.value = errorMessage + console.error('Authentication failed:', errorMessage) + return false + } finally { + isLoading.value = false + } + } + + async function validateToken(): Promise { + if (!token.value) return false + + try { + const validation = await authApi.validateToken(token.value) + + if (!validation.valid) { + clearToken() + return false + } + + // Update expiry if provided + if (validation.expires_at) { + tokenExpiry.value = validation.expires_at + localStorage.setItem(TOKEN_EXPIRY_KEY, validation.expires_at) + } + + return true + } catch (err) { + console.error('Token validation failed:', err) + clearToken() + return false + } + } + + function clearToken() { + token.value = null + tokenExpiry.value = null + tokenResources.value = [] + + localStorage.removeItem(TOKEN_STORAGE_KEY) + localStorage.removeItem(TOKEN_EXPIRY_KEY) + localStorage.removeItem(TOKEN_RESOURCES_KEY) + } + + function loadTokenFromStorage() { + const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY) + const storedExpiry = localStorage.getItem(TOKEN_EXPIRY_KEY) + const storedResources = localStorage.getItem(TOKEN_RESOURCES_KEY) + + if (storedToken && storedExpiry) { + // Check if token is still valid + const expiryTime = new Date(storedExpiry).getTime() + const currentTime = new Date().getTime() + + if (currentTime < expiryTime) { + token.value = storedToken + tokenExpiry.value = storedExpiry + + if (storedResources) { + try { + tokenResources.value = JSON.parse(storedResources) + } catch { + tokenResources.value = [] + } + } + + console.log('Loaded valid token from storage') + return true + } else { + // Token expired, clear it + clearToken() + console.log('Stored token was expired, cleared') + } + } + + return false + } + + async function ensureAuthenticated(): Promise { + // Try to load from storage first + if (loadTokenFromStorage() && isAuthenticated.value) { + return true + } + + // Generate new token + return await generateToken() + } + + // Get auth header for API requests + function getAuthHeader(): Record { + if (token.value) { + return { Authorization: `Bearer ${token.value}` } + } + return {} + } + + // Initialize on store creation + loadTokenFromStorage() + + return { + // State + token: computed(() => token.value), + tokenExpiry: computed(() => tokenExpiry.value), + tokenResources: computed(() => tokenResources.value), + isLoading: computed(() => isLoading.value), + error: computed(() => error.value), + + // Computed + isAuthenticated, + timeUntilExpiry, + + // Actions + generateToken, + validateToken, + clearToken, + loadTokenFromStorage, + ensureAuthenticated, + getAuthHeader + } +}) \ No newline at end of file diff --git a/src/stores/settings.ts b/src/stores/settings.ts index ca8a560..33f5977 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -43,18 +43,18 @@ const DEFAULT_SETTINGS: AppSettings = { autoRefresh: true, refreshInterval: 5, dateFormat: 'relative', - timeFormat: '12h' + timeFormat: '12h', }, notifications: { enabled: true, sound: true, desktop: false, email: false, - criticalOnly: false + criticalOnly: false, }, autoConnect: true, - websocketUrl: 'ws://192.168.1.73:8000/ws', - developerMode: false + websocketUrl: 'ws://localhost:8000/ws', + developerMode: false, } export const useSettingsStore = defineStore('settings', () => { @@ -103,11 +103,11 @@ export const useSettingsStore = defineStore('settings', () => { function updateSetting(path: string, value: any) { const keys = path.split('.') let current: any = settings - + for (let i = 0; i < keys.length - 1; i++) { current = current[keys[i]] } - + current[keys[keys.length - 1]] = value saveSettings() } @@ -116,12 +116,12 @@ export const useSettingsStore = defineStore('settings', () => { function getSetting(path: string): any { const keys = path.split('.') let current: any = settings - + for (const key of keys) { current = current[key] if (current === undefined) break } - + return current } @@ -150,7 +150,7 @@ export const useSettingsStore = defineStore('settings', () => { // Theme helpers function applyTheme() { const root = document.documentElement - + if (settings.theme === 'dark') { root.classList.add('dark') } else if (settings.theme === 'light') { @@ -206,10 +206,14 @@ export const useSettingsStore = defineStore('settings', () => { // Auto-save on changes (debounced) let saveTimeout: number | undefined - watch(settings, () => { - if (saveTimeout) clearTimeout(saveTimeout) - saveTimeout = window.setTimeout(saveSettings, 500) - }, { deep: true }) + watch( + settings, + () => { + if (saveTimeout) clearTimeout(saveTimeout) + saveTimeout = window.setTimeout(saveSettings, 500) + }, + { deep: true }, + ) } // Get available languages @@ -218,7 +222,7 @@ export const useSettingsStore = defineStore('settings', () => { { code: 'en', name: 'English', nativeName: 'English' }, { code: 'es', name: 'Spanish', nativeName: 'Espaรฑol' }, { code: 'fr', name: 'French', nativeName: 'Franรงais' }, - { code: 'de', name: 'German', nativeName: 'Deutsch' } + { code: 'de', name: 'German', nativeName: 'Deutsch' }, ] } @@ -227,31 +231,31 @@ export const useSettingsStore = defineStore('settings', () => { return [ { value: 'system', label: 'System Default', icon: '๐Ÿ”„' }, { value: 'light', label: 'Light Mode', icon: 'โ˜€๏ธ' }, - { value: 'dark', label: 'Dark Mode', icon: '๐ŸŒ™' } + { value: 'dark', label: 'Dark Mode', icon: '๐ŸŒ™' }, ] } // Get navigation mode options function getNavigationModeOptions() { return [ - { - value: 'hover', - label: 'Show on Hover', + { + value: 'hover', + label: 'Show on Hover', description: 'Navigation appears when hovering near bottom (desktop only)', - icon: '๐Ÿ‘†' + icon: '๐Ÿ‘†', }, - { - value: 'always', - label: 'Always Visible', + { + value: 'always', + label: 'Always Visible', description: 'Navigation is permanently visible', - icon: '๐Ÿ‘๏ธ' + icon: '๐Ÿ‘๏ธ', }, - { - value: 'hidden', - label: 'Hidden', + { + value: 'hidden', + label: 'Hidden', description: 'Navigation is completely hidden', - icon: '๐Ÿซฅ' - } + icon: '๐Ÿซฅ', + }, ] } @@ -260,7 +264,7 @@ export const useSettingsStore = defineStore('settings', () => { settings, isLoading, lastSaved, - + // Actions loadSettings, saveSettings, @@ -273,10 +277,10 @@ export const useSettingsStore = defineStore('settings', () => { isValidWebSocketUrl, requestNotificationPermission, initialize, - + // Getters getAvailableLanguages, getThemeOptions, - getNavigationModeOptions + getNavigationModeOptions, } -}) \ No newline at end of file +}) diff --git a/src/views/AIOptimizationView.vue b/src/views/AIOptimizationView.vue index 215d507..7c4c78e 100644 --- a/src/views/AIOptimizationView.vue +++ b/src/views/AIOptimizationView.vue @@ -4,11 +4,13 @@

AI Optimization

-

Leverage artificial intelligence to optimize energy consumption and building operations

+

+ Leverage artificial intelligence to optimize energy consumption and building operations +

-
@@ -93,35 +95,61 @@
Predictive Temperature Control -
Occupancy-based Scheduling -
Weather-based Adjustments -
@@ -163,35 +191,65 @@
Daylight Harvesting -
Motion-based Control -
Circadian Rhythm Sync -
@@ -223,7 +281,9 @@
- + Partial
@@ -233,35 +293,65 @@
Adaptive Access Control -
Anomaly Detection -
Predictive Maintenance -
@@ -303,35 +393,65 @@
CO2-based Ventilation -
Smart Air Filtration -
Humidity Control -
@@ -370,7 +490,7 @@
{{ optimization.description }}
- - -
- - + + + + + \ No newline at end of file