Add analytics dashboard view and API integration
- Add AnalyticsView.vue for real-time API analytics - Update router to include /analytics route - Add Analytics link to BottomNav - Improve MetricCard layout for dashboard consistency - Update main.ts to initialize global auth store - Add Dockerfile and .env for containerization and config - Update README with complete API and architecture overview - Disable Tailwind in main.scss for SCSS-only styling
This commit is contained in:
5
.env
Normal file
5
.env
Normal file
@@ -0,0 +1,5 @@
|
||||
# API Configuration
|
||||
VITE_API_BASE_URL=http://localhost:8000
|
||||
|
||||
# WebSocket Configuration
|
||||
VITE_WS_URL=ws://localhost:8000/ws
|
||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM node:lts-alpine AS node
|
||||
|
||||
# install simple http server for serving static content
|
||||
RUN npm install -g http-server
|
||||
|
||||
# make the 'app' folder the current working directory
|
||||
WORKDIR /app
|
||||
|
||||
# copy both 'package.json' and 'package-lock.json' (if available)
|
||||
COPY package*.json ./
|
||||
|
||||
# install project dependencies
|
||||
RUN npm install
|
||||
|
||||
# copy project files and folders to the current working directory (i.e. 'app' folder)
|
||||
COPY . .
|
||||
|
||||
# build app for production with minification
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 8080
|
||||
CMD [ "http-server", "dist" ]
|
||||
409
README.md
409
README.md
@@ -169,3 +169,412 @@ The frontend will be available at `http://localhost:5173`
|
||||
---
|
||||
|
||||
⚡ by gecad for smarter, more efficient buildings.
|
||||
|
||||
Backend Endpoints
|
||||
|
||||
# Complete Energy Management System Overview
|
||||
|
||||
## 🏆 **Successfully Integrated: Original Dashboard + tiocps + Microservices**
|
||||
|
||||
This implementation successfully combines:
|
||||
- **Original Dashboard**: Sensor management, room creation, real-time data, analytics
|
||||
- **tiocps/iot-building-monitoring**: Advanced energy features, IoT control, demand response
|
||||
- **Modern Architecture**: Microservices, containerization, scalability
|
||||
|
||||
## 🏗️ **Complete Architecture (8 Services)**
|
||||
|
||||
```
|
||||
🌐 Frontend Applications
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ API Gateway │ ← Single Entry Point
|
||||
│ (8000) │ Authentication & Routing
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ Token │ │ Sensor │ │ Battery │
|
||||
│ Service │ │ Service │ │ Service │
|
||||
│ (8001) │ │ (8007) │ │ (8002) │
|
||||
│ │ │ │ │ │
|
||||
│• JWT Auth │ │• Sensors │ │• Charging │
|
||||
│• Permissions│ │• Rooms │ │• Health │
|
||||
│• Resources│ │• Analytics│ │• Control │
|
||||
└───────────┘ │• WebSocket│ └───────────┘
|
||||
│• Export │
|
||||
└───────────┘
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ Demand │ │ P2P │ │ Forecast │
|
||||
│ Response │ │ Trading │ │ Service │
|
||||
│ (8003) │ │ (8004) │ │ (8005) │
|
||||
│ │ │ │ │ │
|
||||
│• Grid │ │• Market │ │• ML Models│
|
||||
│• Events │ │• Trading │ │• Predict │
|
||||
│• Load Mgmt│ │• P2P Trans│ │• Analysis │
|
||||
└───────────┘ └───────────┘ └───────────┘
|
||||
│
|
||||
┌─────▼─────┐
|
||||
│ IoT │
|
||||
│ Control │
|
||||
│ (8006) │
|
||||
│ │
|
||||
│• Devices │
|
||||
│• Automation│
|
||||
│• Instructions│
|
||||
└───────────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ MongoDB │ │ Redis │ │ WebSocket │
|
||||
│ Database │ │ Cache & │ │ Real-time │
|
||||
│ (27017) │ │ Events │ │ Streaming │
|
||||
└───────────┘ │ (6379) │ └───────────┘
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
## 📋 **Service Inventory & Capabilities**
|
||||
|
||||
### **🚪 API Gateway (Port 8000)**
|
||||
**Role**: Central entry point and orchestration
|
||||
**Key Features**:
|
||||
- Request routing to all services
|
||||
- JWT token validation
|
||||
- Load balancing and health checks
|
||||
- Rate limiting and monitoring
|
||||
- WebSocket proxy for real-time data
|
||||
|
||||
**Endpoints**:
|
||||
```
|
||||
GET /health # System health
|
||||
GET /services/status # All services status
|
||||
GET /stats # Gateway statistics
|
||||
GET /api/v1/overview # Complete system overview
|
||||
WS /ws # WebSocket proxy
|
||||
```
|
||||
|
||||
### **🔐 Token Service (Port 8001)**
|
||||
**Role**: Authentication and authorization
|
||||
**Key Features**:
|
||||
- JWT token generation and validation
|
||||
- Resource-based permissions
|
||||
- Token lifecycle management
|
||||
- Auto-expiration and cleanup
|
||||
|
||||
**Endpoints**:
|
||||
```
|
||||
POST /tokens/generate # Create JWT token
|
||||
POST /tokens/validate # Verify token
|
||||
POST /tokens/save # Store token
|
||||
POST /tokens/revoke # Revoke token
|
||||
GET /tokens # List tokens
|
||||
```
|
||||
|
||||
### **📊 Sensor Service (Port 8007) - 🎯 CORE DASHBOARD**
|
||||
**Role**: Complete original dashboard functionality + enhancements
|
||||
**Key Features**:
|
||||
- **Sensor Management**: CRUD operations, metadata, status
|
||||
- **Room Management**: Room creation, metrics, occupancy
|
||||
- **Real-time Data**: WebSocket streaming, live updates
|
||||
- **Analytics**: Energy consumption, environmental metrics
|
||||
- **Data Export**: Historical data, multiple formats
|
||||
- **Event Management**: System alerts, notifications
|
||||
|
||||
**Endpoints**:
|
||||
```
|
||||
# Original Dashboard APIs (Enhanced)
|
||||
GET/POST/PUT/DELETE /sensors/* # Sensor management
|
||||
GET/POST /rooms/* # Room management
|
||||
WS /ws # Real-time WebSocket
|
||||
POST /data/query # Advanced analytics
|
||||
GET /analytics/summary # System analytics
|
||||
GET /export # Data export
|
||||
GET /events # System events
|
||||
|
||||
# Enhanced Features
|
||||
POST /data/ingest # Real-time data ingestion
|
||||
GET /analytics/energy # Energy-specific analytics
|
||||
GET /rooms/{name}/data # Room historical data
|
||||
```
|
||||
|
||||
### **🔋 Battery Service (Port 8002)**
|
||||
**Role**: Energy storage management
|
||||
**Key Features**:
|
||||
- Battery monitoring and control
|
||||
- Charging/discharging optimization
|
||||
- Health monitoring and alerts
|
||||
- Performance analytics
|
||||
|
||||
**Endpoints**:
|
||||
```
|
||||
GET /batteries # All batteries
|
||||
POST /batteries/{id}/charge # Charge battery
|
||||
POST /batteries/{id}/discharge # Discharge battery
|
||||
POST /batteries/{id}/optimize # Smart optimization
|
||||
GET /batteries/analytics/summary # System analytics
|
||||
```
|
||||
|
||||
### **⚡ Demand Response Service (Port 8003)**
|
||||
**Role**: Grid interaction and load management
|
||||
**Key Features**:
|
||||
- Demand response event management
|
||||
- Load reduction coordination
|
||||
- Flexibility forecasting
|
||||
- Auto-response configuration
|
||||
|
||||
**Endpoints**:
|
||||
```
|
||||
POST /invitations/send # Send DR invitation
|
||||
GET /invitations/unanswered # Pending invitations
|
||||
POST /invitations/answer # Respond to invitation
|
||||
GET /flexibility/current # Available flexibility
|
||||
POST /load-reduction/execute # Execute load reduction
|
||||
```
|
||||
|
||||
### **🤝 P2P Trading Service (Port 8004)**
|
||||
**Role**: Peer-to-peer energy marketplace
|
||||
**Key Features**:
|
||||
- Energy trading marketplace
|
||||
- Bid/ask management
|
||||
- Transaction processing
|
||||
- Market analytics
|
||||
|
||||
### **📈 Forecasting Service (Port 8005)**
|
||||
**Role**: ML-based predictions
|
||||
**Key Features**:
|
||||
- Consumption/generation forecasting
|
||||
- Historical data analysis
|
||||
- Model training and optimization
|
||||
- Predictive analytics
|
||||
|
||||
### **🏠 IoT Control Service (Port 8006)**
|
||||
**Role**: Device management and automation
|
||||
**Key Features**:
|
||||
- Device registration and control
|
||||
- Automation rules and scheduling
|
||||
- Remote device instructions
|
||||
- Integration with other services
|
||||
|
||||
## 🔄 **Complete API Reference**
|
||||
|
||||
### **Original Dashboard APIs (Preserved & Enhanced)**
|
||||
All original dashboard functionality is preserved and enhanced:
|
||||
|
||||
```typescript
|
||||
// Sensor Management - Now with tiocps enhancements
|
||||
GET /api/v1/sensors
|
||||
POST /api/v1/sensors
|
||||
PUT /api/v1/sensors/{id}
|
||||
DELETE /api/v1/sensors/{id}
|
||||
GET /api/v1/sensors/{id}/data
|
||||
|
||||
// Room Management - Now with energy flexibility
|
||||
GET /api/v1/rooms
|
||||
POST /api/v1/rooms
|
||||
GET /api/v1/rooms/{name}
|
||||
GET /api/v1/rooms/{name}/data
|
||||
|
||||
// Real-time Data - Enhanced with multi-metrics
|
||||
WebSocket /ws
|
||||
|
||||
// Analytics - Enhanced with energy management
|
||||
GET /api/v1/analytics/summary
|
||||
GET /api/v1/analytics/energy
|
||||
POST /api/v1/data/query
|
||||
|
||||
// Data Export - Enhanced with all sensor types
|
||||
GET /api/v1/export
|
||||
|
||||
// System Events - Integrated with all services
|
||||
GET /api/v1/events
|
||||
```
|
||||
|
||||
### **New tiocps-based APIs**
|
||||
Complete energy management capabilities:
|
||||
|
||||
```typescript
|
||||
// Authentication (New)
|
||||
POST /api/v1/tokens/generate
|
||||
POST /api/v1/tokens/validate
|
||||
|
||||
// Battery Management (New)
|
||||
GET /api/v1/batteries
|
||||
POST /api/v1/batteries/{id}/charge
|
||||
GET /api/v1/batteries/analytics/summary
|
||||
|
||||
// Demand Response (New)
|
||||
POST /api/v1/demand-response/invitations/send
|
||||
GET /api/v1/demand-response/flexibility/current
|
||||
|
||||
// P2P Trading (New)
|
||||
POST /api/v1/p2p/transactions
|
||||
GET /api/v1/p2p/market/status
|
||||
|
||||
// Forecasting (New)
|
||||
GET /api/v1/forecast/consumption
|
||||
GET /api/v1/forecast/generation
|
||||
|
||||
// IoT Control (New)
|
||||
POST /api/v1/iot/devices/{id}/instructions
|
||||
GET /api/v1/iot/devices/summary
|
||||
```
|
||||
|
||||
## 🚀 **Deployment & Usage**
|
||||
|
||||
### **Quick Start**
|
||||
```bash
|
||||
# Clone and navigate
|
||||
cd microservices/
|
||||
|
||||
# Deploy complete system
|
||||
./deploy.sh deploy
|
||||
|
||||
# Check system status
|
||||
./deploy.sh status
|
||||
|
||||
# View logs
|
||||
./deploy.sh logs
|
||||
```
|
||||
|
||||
### **Service Access Points**
|
||||
```
|
||||
🌐 API Gateway: http://localhost:8000
|
||||
🔐 Authentication: http://localhost:8001
|
||||
📊 Sensors/Rooms: http://localhost:8007
|
||||
🔋 Batteries: http://localhost:8002
|
||||
⚡ Demand Response: http://localhost:8003
|
||||
🤝 P2P Trading: http://localhost:8004
|
||||
📈 Forecasting: http://localhost:8005
|
||||
🏠 IoT Control: http://localhost:8006
|
||||
|
||||
📡 WebSocket: ws://localhost:8007/ws
|
||||
📈 System Health: http://localhost:8000/health
|
||||
📊 System Overview: http://localhost:8000/api/v1/overview
|
||||
```
|
||||
|
||||
### **Example Usage**
|
||||
|
||||
**1. Complete Dashboard Workflow (Original + Enhanced)**
|
||||
```bash
|
||||
# 1. Get authentication token
|
||||
TOKEN=$(curl -s -X POST "http://localhost:8000/api/v1/tokens/generate" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "dashboard_user", "list_of_resources": ["sensors", "rooms", "analytics"]}' \
|
||||
| jq -r '.token')
|
||||
|
||||
# 2. Create a room
|
||||
curl -X POST "http://localhost:8000/api/v1/rooms" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "Conference Room A", "floor": "2nd", "capacity": 20}'
|
||||
|
||||
# 3. Register sensors
|
||||
curl -X POST "http://localhost:8000/api/v1/sensors" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"sensor_id": "TEMP_001",
|
||||
"name": "Conference Room Temperature",
|
||||
"sensor_type": "temperature",
|
||||
"room": "Conference Room A"
|
||||
}'
|
||||
|
||||
# 4. Get real-time analytics
|
||||
curl "http://localhost:8000/api/v1/analytics/summary" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# 5. Export data
|
||||
curl "http://localhost:8000/api/v1/export?start_time=1704067200&end_time=1704153600" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**2. Advanced Energy Management (New tiocps Features)**
|
||||
```bash
|
||||
# Battery management
|
||||
curl -X POST "http://localhost:8000/api/v1/batteries/BATT001/charge" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"power_kw": 50, "duration_minutes": 120}'
|
||||
|
||||
# Demand response event
|
||||
curl -X POST "http://localhost:8000/api/v1/demand-response/invitations/send" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{
|
||||
"event_time": "2024-01-10T14:00:00Z",
|
||||
"load_kwh": 100,
|
||||
"duration_minutes": 60,
|
||||
"iots": ["DEVICE_001", "DEVICE_002"]
|
||||
}'
|
||||
|
||||
# Get system flexibility
|
||||
curl "http://localhost:8000/api/v1/demand-response/flexibility/current" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
## 📊 **System Monitoring**
|
||||
|
||||
### **Health Monitoring**
|
||||
```bash
|
||||
# Overall system health
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Individual service health
|
||||
curl http://localhost:8001/health # Token Service
|
||||
curl http://localhost:8007/health # Sensor Service
|
||||
curl http://localhost:8002/health # Battery Service
|
||||
# ... etc for all services
|
||||
```
|
||||
|
||||
### **Performance Monitoring**
|
||||
```bash
|
||||
# API Gateway statistics
|
||||
curl http://localhost:8000/stats
|
||||
|
||||
# Service status overview
|
||||
curl http://localhost:8000/services/status
|
||||
|
||||
# Complete system overview
|
||||
curl http://localhost:8000/api/v1/overview
|
||||
```
|
||||
|
||||
## 🎯 **Key Integration Success Factors**
|
||||
|
||||
### **✅ Backward Compatibility**
|
||||
- All original dashboard APIs preserved
|
||||
- Existing frontend applications work unchanged
|
||||
- Gradual migration path available
|
||||
|
||||
### **✅ Enhanced Functionality**
|
||||
- Original sensors enhanced with tiocps capabilities
|
||||
- Room metrics include energy and flexibility data
|
||||
- Analytics enhanced with energy management insights
|
||||
|
||||
### **✅ Scalability & Reliability**
|
||||
- Independent service scaling
|
||||
- Fault isolation between services
|
||||
- Health checks and automatic recovery
|
||||
- Load balancing and connection pooling
|
||||
|
||||
### **✅ Developer Experience**
|
||||
- Single-command deployment
|
||||
- Unified API documentation
|
||||
- Consistent error handling
|
||||
- Comprehensive logging
|
||||
|
||||
### **✅ Production Readiness**
|
||||
- Docker containerization
|
||||
- Service discovery and health checks
|
||||
- Authentication and authorization
|
||||
- Monitoring and alerting capabilities
|
||||
|
||||
## 🔮 **Future Enhancements**
|
||||
|
||||
The integrated system provides a solid foundation for:
|
||||
- **Kubernetes deployment** for cloud-native scaling
|
||||
- **Advanced ML models** for energy optimization
|
||||
- **Mobile applications** using the unified API
|
||||
- **Third-party integrations** via standardized APIs
|
||||
- **Multi-tenant support** with enhanced authentication
|
||||
|
||||
This complete integration successfully delivers a production-ready energy management platform that combines the best of dashboard usability with advanced energy management capabilities, all built on a modern, scalable microservices architecture.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use 'tailwindcss';
|
||||
/* @use 'tailwindcss';
|
||||
@import 'abstracts/variables';
|
||||
@import 'abstracts/mixins';
|
||||
|
||||
@@ -20,4 +20,4 @@
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities; */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="bg-white rounded-2xl shadow-sm flex flex-col justify-between aspect-square p-4">
|
||||
<div class="bg-white rounded-2xl shadow-sm flex flex-col justify-between h-full w-full p-4">
|
||||
<h6 class="text-sm font-bold text-gray-500">{{ title }}</h6>
|
||||
<div class="flex-grow flex items-center justify-start">
|
||||
<p class="text-gray-900 font-bold text-2xl">
|
||||
|
||||
@@ -73,6 +73,27 @@
|
||||
<span class="text-xs">AI Optimize</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link
|
||||
to="/analytics"
|
||||
class="flex flex-col items-center font-medium"
|
||||
:class="
|
||||
$route.name === 'analytics'
|
||||
? 'text-indigo-600'
|
||||
: 'text-gray-600 hover:text-indigo-600'
|
||||
"
|
||||
>
|
||||
<svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-xs">Analytics</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link
|
||||
to="/settings"
|
||||
|
||||
28
src/main.ts
28
src/main.ts
@@ -8,9 +8,33 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
|
||||
// Initialize auth store and make it globally available for API client
|
||||
import { useAuthStore } from './stores/auth'
|
||||
app.component('v-chart', ECharts)
|
||||
app.mount('#app')
|
||||
|
||||
// Mount the app
|
||||
const mountedApp = app.mount('#app')
|
||||
|
||||
// Make auth store globally available after app is mounted
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const authStore = useAuthStore()
|
||||
;(window as any).__AUTH_STORE__ = authStore
|
||||
|
||||
// Ensure authentication on app start
|
||||
authStore.ensureAuthenticated().then((success) => {
|
||||
if (success) {
|
||||
console.log('Authentication initialized successfully')
|
||||
} else {
|
||||
console.warn('Failed to initialize authentication')
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize auth store:', error)
|
||||
}
|
||||
}, 100)
|
||||
|
||||
@@ -3,6 +3,7 @@ import HomeView from '../views/HomeView.vue'
|
||||
import SensorManagementView from '../views/SensorManagementView.vue'
|
||||
import AIOptimizationView from '../views/AIOptimizationView.vue'
|
||||
import SettingsView from '../views/SettingsView.vue'
|
||||
import AnalyticsView from '../views/AnalyticsView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -27,6 +28,11 @@ const router = createRouter({
|
||||
name: 'settings',
|
||||
component: SettingsView,
|
||||
},
|
||||
{
|
||||
path: '/analytics',
|
||||
name: 'analytics',
|
||||
component: AnalyticsView,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
267
src/views/AnalyticsView.vue
Normal file
267
src/views/AnalyticsView.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<div class="px-4 py-6 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">API Dashboard</h1>
|
||||
<p class="text-gray-600 mt-2">Real-time data from backend APIs</p>
|
||||
</div>
|
||||
|
||||
<!-- API Status Section -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-blue-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">System Status</p>
|
||||
<p class="text-lg font-semibold" :class="healthStatus?.status === 'healthy' ? 'text-green-600' : 'text-red-600'">
|
||||
{{ healthStatus?.status || 'Unknown' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-green-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">Total Sensors</p>
|
||||
<p class="text-lg font-semibold text-gray-900">{{ healthStatus?.total_sensors || 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-yellow-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">Active Sensors</p>
|
||||
<p class="text-lg font-semibold text-gray-900">{{ healthStatus?.active_sensors || 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-purple-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">Total Readings</p>
|
||||
<p class="text-lg font-semibold text-gray-900">{{ formatNumber(healthStatus?.total_readings || 0) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading States -->
|
||||
<div v-if="energyStore.apiLoading" class="bg-white rounded-lg shadow p-6 mb-8">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<p class="ml-3 text-gray-600">Loading API data...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error States -->
|
||||
<div v-if="energyStore.apiError" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-8">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800">API Error</h3>
|
||||
<p class="mt-1 text-sm text-red-700">{{ energyStore.apiError }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analytics Summary -->
|
||||
<div v-if="analyticsData.summary" class="bg-white rounded-lg shadow p-6 mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Analytics Summary (Last 24 Hours)</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600 mb-2">Total Energy Consumption</p>
|
||||
<p class="text-2xl font-bold text-blue-600">
|
||||
{{ analyticsData.summary.total_energy_consumption.value.toFixed(2) }}
|
||||
{{ analyticsData.summary.total_energy_consumption.unit }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600 mb-2">Average Power</p>
|
||||
<p class="text-2xl font-bold text-green-600">
|
||||
{{ analyticsData.summary.average_power.value.toFixed(2) }}
|
||||
{{ analyticsData.summary.average_power.unit }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600 mb-2">Peak Power</p>
|
||||
<p class="text-2xl font-bold text-red-600">
|
||||
{{ analyticsData.summary.peak_power.value.toFixed(2) }}
|
||||
{{ analyticsData.summary.peak_power.unit }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
at {{ new Date(analyticsData.summary.peak_power.timestamp * 1000).toLocaleString() }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sensors Section -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900">API Sensors</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div v-if="apiSensors.length === 0" class="text-center text-gray-500 py-8">
|
||||
No sensors found from API
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div v-for="sensor in apiSensors" :key="sensor.sensor_id"
|
||||
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900">{{ sensor.sensor_id }}</p>
|
||||
<p class="text-sm text-gray-500">{{ sensor.room || 'No room assigned' }}</p>
|
||||
<p class="text-xs text-gray-400">{{ sensor.sensor_type }} • {{ sensor.total_readings }} readings</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span :class="[
|
||||
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||
sensor.status === 'online' ? 'bg-green-100 text-green-800' :
|
||||
sensor.status === 'offline' ? 'bg-red-100 text-red-800' :
|
||||
'bg-yellow-100 text-yellow-800'
|
||||
]">
|
||||
{{ sensor.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rooms Section -->
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900">API Rooms</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div v-if="apiRooms.length === 0" class="text-center text-gray-500 py-8">
|
||||
No rooms found from API
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div v-for="room in apiRooms" :key="room.room"
|
||||
class="p-3 bg-gray-50 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p class="font-medium text-gray-900">{{ room.room }}</p>
|
||||
<span class="text-sm text-gray-500">{{ room.sensor_count }} sensors</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 space-y-1">
|
||||
<p>Types: {{ room.sensor_types.join(', ') }}</p>
|
||||
<div v-if="room.latest_metrics">
|
||||
<span v-if="room.latest_metrics.energy" class="mr-4">
|
||||
Energy: {{ room.latest_metrics.energy.current }} {{ room.latest_metrics.energy.unit }}
|
||||
</span>
|
||||
<span v-if="room.latest_metrics.co2">
|
||||
CO2: {{ room.latest_metrics.co2.current }} {{ room.latest_metrics.co2.unit }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">API Actions</h2>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button @click="refreshAllData"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
:disabled="energyStore.apiLoading">
|
||||
<svg v-if="!energyStore.apiLoading" class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<div v-else class="animate-spin -ml-1 mr-2 h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div>
|
||||
Refresh All Data
|
||||
</button>
|
||||
|
||||
<button @click="fetchSensorsOnly"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
:disabled="energyStore.apiLoading">
|
||||
Fetch Sensors
|
||||
</button>
|
||||
|
||||
<button @click="fetchRoomsOnly"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
:disabled="energyStore.apiLoading">
|
||||
Fetch Rooms
|
||||
</button>
|
||||
|
||||
<button @click="fetchAnalyticsOnly"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
:disabled="energyStore.apiLoading">
|
||||
Fetch Analytics
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useEnergyStore } from '@/stores/energy'
|
||||
|
||||
const energyStore = useEnergyStore()
|
||||
|
||||
// Computed properties
|
||||
const apiSensors = computed(() => energyStore.apiSensors)
|
||||
const apiRooms = computed(() => energyStore.apiRooms)
|
||||
const analyticsData = computed(() => energyStore.analyticsData)
|
||||
const healthStatus = computed(() => energyStore.healthStatus)
|
||||
|
||||
// Helper functions
|
||||
const formatNumber = (num: number): string => {
|
||||
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'
|
||||
if (num >= 1000) return (num / 1000).toFixed(1) + 'K'
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
// Action functions
|
||||
const refreshAllData = async () => {
|
||||
await energyStore.initializeFromApi()
|
||||
}
|
||||
|
||||
const fetchSensorsOnly = async () => {
|
||||
await energyStore.fetchApiSensors()
|
||||
}
|
||||
|
||||
const fetchRoomsOnly = async () => {
|
||||
await energyStore.fetchApiRooms()
|
||||
}
|
||||
|
||||
const fetchAnalyticsOnly = async () => {
|
||||
await energyStore.fetchAnalyticsSummary()
|
||||
}
|
||||
|
||||
// Initialize data on mount
|
||||
onMounted(async () => {
|
||||
await energyStore.initializeFromApi()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user