Fix jwt-token
This commit is contained in:
5
.env
5
.env
@@ -17,6 +17,11 @@ TWILIO_ACCOUNT_SID=your_twilio_account_sid_here
|
|||||||
TWILIO_AUTH_TOKEN=your_twilio_auth_token_here
|
TWILIO_AUTH_TOKEN=your_twilio_auth_token_here
|
||||||
TWILIO_PHONE_NUMBER=your_twilio_phone_number_here
|
TWILIO_PHONE_NUMBER=your_twilio_phone_number_here
|
||||||
|
|
||||||
|
# Health Probe Simulator Configuration
|
||||||
|
PROBE_FAILRATE=30
|
||||||
|
PROBE_INTERVAL_SECONDS=60
|
||||||
|
API_BASE_URL=https://selfservice.cqers.com/drones/api
|
||||||
|
|
||||||
# Optional: Override default settings
|
# Optional: Override default settings
|
||||||
# NODE_ENV=production
|
# NODE_ENV=production
|
||||||
# CORS_ORIGIN=http://localhost:3000
|
# CORS_ORIGIN=http://localhost:3000
|
||||||
|
|||||||
18
Dockerfile.healthprobe
Normal file
18
Dockerfile.healthprobe
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install required Python packages
|
||||||
|
RUN pip install --no-cache-dir requests
|
||||||
|
|
||||||
|
# Copy the health probe simulator script
|
||||||
|
COPY health_probe_simulator.py .
|
||||||
|
|
||||||
|
# Set environment variables with defaults
|
||||||
|
ENV PROBE_FAILRATE=30
|
||||||
|
ENV PROBE_INTERVAL_SECONDS=60
|
||||||
|
ENV API_BASE_URL=https://selfservice.cqers.com/drones/api
|
||||||
|
|
||||||
|
# Run the health probe simulator
|
||||||
|
CMD ["python", "health_probe_simulator.py"]
|
||||||
@@ -142,6 +142,25 @@ services:
|
|||||||
- simulation
|
- simulation
|
||||||
command: python drone_simulator.py --devices 5 --duration 3600
|
command: python drone_simulator.py --devices 5 --duration 3600
|
||||||
|
|
||||||
|
# Health Probe Simulator (Continuous Device Heartbeats)
|
||||||
|
healthprobe:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.healthprobe
|
||||||
|
container_name: drone-detection-healthprobe
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PROBE_FAILRATE: ${PROBE_FAILRATE:-30}
|
||||||
|
PROBE_INTERVAL_SECONDS: ${PROBE_INTERVAL_SECONDS:-60}
|
||||||
|
API_BASE_URL: http://backend:3001/api
|
||||||
|
networks:
|
||||||
|
- drone-network
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
profiles:
|
||||||
|
- healthprobe
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|||||||
174
docs/HEALTH_PROBE_SIMULATOR.md
Normal file
174
docs/HEALTH_PROBE_SIMULATOR.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# Health Probe Simulator
|
||||||
|
|
||||||
|
The Health Probe Simulator continuously sends heartbeat messages from all devices to test the offline detection feature of the drone detection system.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🔄 **Continuous Monitoring**: Sends heartbeats every minute (configurable)
|
||||||
|
- 📊 **Configurable Failure Rate**: Simulates device failures to test offline detection
|
||||||
|
- 🎯 **Real Device Integration**: Uses actual devices from your system
|
||||||
|
- 📈 **Statistics Reporting**: Shows success/failure rates for each cycle
|
||||||
|
- 🐳 **Docker Support**: Can run as a container alongside the main system
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `PROBE_FAILRATE` | `30` | Percentage of probes that should fail (0-100) |
|
||||||
|
| `PROBE_INTERVAL_SECONDS` | `60` | Interval between probe cycles in seconds |
|
||||||
|
| `API_BASE_URL` | `https://selfservice.cqers.com/drones/api` | API endpoint URL |
|
||||||
|
|
||||||
|
### .env File
|
||||||
|
|
||||||
|
Add these lines to your `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Health Probe Simulator Configuration
|
||||||
|
PROBE_FAILRATE=30
|
||||||
|
PROBE_INTERVAL_SECONDS=60
|
||||||
|
API_BASE_URL=https://selfservice.cqers.com/drones/api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Option 1: Docker Compose (Recommended)
|
||||||
|
|
||||||
|
Start the health probe simulator as a Docker service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start with health probe simulator
|
||||||
|
docker-compose --profile healthprobe up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f healthprobe
|
||||||
|
|
||||||
|
# Stop the service
|
||||||
|
docker-compose --profile healthprobe down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Local Python Script
|
||||||
|
|
||||||
|
Run directly with Python:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
./start-healthprobe.sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
start-healthprobe.bat
|
||||||
|
|
||||||
|
# Or directly with Python
|
||||||
|
python3 health_probe_simulator.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Manual Docker Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the health probe image
|
||||||
|
docker build -f Dockerfile.healthprobe -t health-probe-simulator .
|
||||||
|
|
||||||
|
# Run the container
|
||||||
|
docker run -d \
|
||||||
|
--name health-probe \
|
||||||
|
--network uamils_drone-network \
|
||||||
|
-e PROBE_FAILRATE=30 \
|
||||||
|
-e PROBE_INTERVAL_SECONDS=60 \
|
||||||
|
-e API_BASE_URL=http://backend:3001/api \
|
||||||
|
health-probe-simulator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Example
|
||||||
|
|
||||||
|
```
|
||||||
|
🏥 DRONE DETECTION SYSTEM - HEALTH PROBE SIMULATOR
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
📋 Configuration:
|
||||||
|
PROBE_FAILRATE: 30%
|
||||||
|
PROBE_INTERVAL_SECONDS: 60s
|
||||||
|
API_BASE_URL: https://selfservice.cqers.com/drones/api
|
||||||
|
|
||||||
|
🔧 Health Probe Simulator Configuration:
|
||||||
|
API URL: https://selfservice.cqers.com/drones/api
|
||||||
|
Probe Interval: 60 seconds
|
||||||
|
Failure Rate: 30%
|
||||||
|
|
||||||
|
📡 Fetched 4 devices for health monitoring
|
||||||
|
- Device 1941875381: Arlanda Airport Detector (Arlanda Airport Terminal 2)
|
||||||
|
- Device 1725834521: Royal Castle Detector (Royal Castle Stockholm)
|
||||||
|
- Device 1456789123: Muskö Naval Base Detector (Muskö Naval Base)
|
||||||
|
- Device 1987654321: Gothenburg Port Detector (Port of Gothenburg)
|
||||||
|
|
||||||
|
🔄 Starting probe cycle at 2025-08-19 14:30:00
|
||||||
|
Probing 4 devices...
|
||||||
|
|
||||||
|
💓 Device 1941875381: Heartbeat sent successfully (Arlanda Airport Detector - Arlanda Airport Terminal 2)
|
||||||
|
💔 Simulating probe failure for Device 1725834521 (30% failure rate)
|
||||||
|
💓 Device 1456789123: Heartbeat sent successfully (Muskö Naval Base Detector - Muskö Naval Base)
|
||||||
|
💔 Simulating probe failure for Device 1987654321 (30% failure rate)
|
||||||
|
|
||||||
|
📊 Probe Cycle Summary:
|
||||||
|
✅ Successful: 2
|
||||||
|
❌ Failed: 2
|
||||||
|
📈 Success Rate: 50.0%
|
||||||
|
🎯 Target Failure Rate: 30%
|
||||||
|
⏰ Next cycle in 60 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Offline Detection
|
||||||
|
|
||||||
|
The health probe simulator is perfect for testing your system's offline detection capabilities:
|
||||||
|
|
||||||
|
1. **Set Failure Rate**: Adjust `PROBE_FAILRATE` to control how many devices appear offline
|
||||||
|
2. **Monitor Dashboard**: Watch the map view to see devices changing status
|
||||||
|
3. **Check Alerts**: Verify that offline device alerts are triggered
|
||||||
|
4. **Test Recovery**: Failed devices will randomly come back online in subsequent cycles
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- **🧪 Testing**: Verify offline detection works correctly
|
||||||
|
- **🎭 Demo**: Show system behavior during device failures
|
||||||
|
- **📊 Load Testing**: Generate consistent heartbeat traffic
|
||||||
|
- **🔧 Development**: Test heartbeat handling and device status logic
|
||||||
|
|
||||||
|
## Stopping the Simulator
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
```bash
|
||||||
|
docker-compose --profile healthprobe down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local Script
|
||||||
|
Press `Ctrl+C` to stop the simulator gracefully.
|
||||||
|
|
||||||
|
### Docker Container
|
||||||
|
```bash
|
||||||
|
docker stop health-probe
|
||||||
|
docker rm health-probe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Connection Errors**: Check API_BASE_URL and network connectivity
|
||||||
|
2. **Authentication Errors**: Ensure the API endpoint doesn't require authentication for heartbeats
|
||||||
|
3. **High CPU Usage**: Increase PROBE_INTERVAL_SECONDS to reduce frequency
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
Check the container logs for detailed output:
|
||||||
|
```bash
|
||||||
|
docker-compose logs healthprobe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Main System
|
||||||
|
|
||||||
|
The health probe simulator:
|
||||||
|
- ✅ Uses the same heartbeat API as real devices
|
||||||
|
- ✅ Automatically discovers all devices in your system
|
||||||
|
- ✅ Respects your configured offline detection timeouts
|
||||||
|
- ✅ Generates realistic device status changes
|
||||||
|
- ✅ Helps validate your monitoring and alerting systems
|
||||||
187
health_probe_simulator.py
Normal file
187
health_probe_simulator.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Health Check Probe Simulator
|
||||||
|
Continuously sends heartbeat probes from all devices to test offline detection
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
class DeviceProbeSimulator:
|
||||||
|
def __init__(self):
|
||||||
|
self.api_base_url = os.getenv('API_BASE_URL', 'https://selfservice.cqers.com/drones/api')
|
||||||
|
self.probe_interval = int(os.getenv('PROBE_INTERVAL_SECONDS', '60')) # 1 minute default
|
||||||
|
self.probe_failrate = int(os.getenv('PROBE_FAILRATE', '30')) # 30% failure rate
|
||||||
|
self.devices = []
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'HealthProbeSimulator/1.0'
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"🔧 Health Probe Simulator Configuration:")
|
||||||
|
print(f" API URL: {self.api_base_url}")
|
||||||
|
print(f" Probe Interval: {self.probe_interval} seconds")
|
||||||
|
print(f" Failure Rate: {self.probe_failrate}%")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def fetch_devices(self) -> bool:
|
||||||
|
"""Fetch all devices from the API"""
|
||||||
|
try:
|
||||||
|
response = self.session.get(f"{self.api_base_url}/devices")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get('success') and 'data' in data:
|
||||||
|
self.devices = data['data']
|
||||||
|
print(f"📡 Fetched {len(self.devices)} devices for health monitoring")
|
||||||
|
for device in self.devices:
|
||||||
|
print(f" - Device {device['id']}: {device.get('name', 'Unnamed')} ({device.get('location_description', 'No location')})")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ API returned invalid data structure: {data}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"❌ Failed to fetch devices: HTTP {response.status_code}")
|
||||||
|
print(f" Response: {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error fetching devices: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_heartbeat(self, device: Dict) -> bool:
|
||||||
|
"""Send heartbeat for a specific device"""
|
||||||
|
device_id = device['id']
|
||||||
|
|
||||||
|
# Simulate probe failure based on failure rate
|
||||||
|
if random.randint(1, 100) <= self.probe_failrate:
|
||||||
|
print(f"💔 Simulating probe failure for Device {device_id} ({self.probe_failrate}% failure rate)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = {
|
||||||
|
'device_id': device_id,
|
||||||
|
'timestamp': int(time.time()),
|
||||||
|
'status': 'online',
|
||||||
|
'system_info': {
|
||||||
|
'cpu_usage': random.randint(20, 80),
|
||||||
|
'memory_usage': random.randint(30, 90),
|
||||||
|
'disk_usage': random.randint(40, 85),
|
||||||
|
'temperature': random.randint(25, 65),
|
||||||
|
'uptime': random.randint(3600, 86400) # 1 hour to 1 day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.post(
|
||||||
|
f"{self.api_base_url}/devices/heartbeat",
|
||||||
|
json=payload,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code in [200, 201]:
|
||||||
|
device_name = device.get('name', f'Device {device_id}')
|
||||||
|
location = device.get('location_description', 'Unknown location')
|
||||||
|
print(f"💓 Device {device_id}: Heartbeat sent successfully ({device_name} - {location})")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Failed to send heartbeat for Device {device_id}: HTTP {response.status_code}")
|
||||||
|
print(f" Response: {response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
print(f"⏰ Heartbeat timeout for Device {device_id}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error sending heartbeat for Device {device_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run_probe_cycle(self):
|
||||||
|
"""Run one complete probe cycle for all devices"""
|
||||||
|
if not self.devices:
|
||||||
|
print("⚠️ No devices to probe, fetching device list...")
|
||||||
|
if not self.fetch_devices():
|
||||||
|
print("❌ Cannot continue without device list")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n🔄 Starting probe cycle at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
print(f" Probing {len(self.devices)} devices...")
|
||||||
|
|
||||||
|
successful_probes = 0
|
||||||
|
failed_probes = 0
|
||||||
|
|
||||||
|
for device in self.devices:
|
||||||
|
if self.send_heartbeat(device):
|
||||||
|
successful_probes += 1
|
||||||
|
else:
|
||||||
|
failed_probes += 1
|
||||||
|
|
||||||
|
# Small delay between probes to avoid overwhelming the server
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
success_rate = (successful_probes / len(self.devices)) * 100 if self.devices else 0
|
||||||
|
print(f"\n📊 Probe Cycle Summary:")
|
||||||
|
print(f" ✅ Successful: {successful_probes}")
|
||||||
|
print(f" ❌ Failed: {failed_probes}")
|
||||||
|
print(f" 📈 Success Rate: {success_rate:.1f}%")
|
||||||
|
print(f" 🎯 Target Failure Rate: {self.probe_failrate}%")
|
||||||
|
print(f" ⏰ Next cycle in {self.probe_interval} seconds")
|
||||||
|
|
||||||
|
def run_continuous(self):
|
||||||
|
"""Run continuous health check probes"""
|
||||||
|
print("🚀 Starting continuous health probe simulator...")
|
||||||
|
print(" Press Ctrl+C to stop")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Initial device fetch
|
||||||
|
if not self.fetch_devices():
|
||||||
|
print("❌ Failed to fetch initial device list, exiting")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
self.run_probe_cycle()
|
||||||
|
|
||||||
|
# Re-fetch devices every 10 cycles to catch new devices
|
||||||
|
if hasattr(self, 'cycle_count'):
|
||||||
|
self.cycle_count += 1
|
||||||
|
else:
|
||||||
|
self.cycle_count = 1
|
||||||
|
|
||||||
|
if self.cycle_count % 10 == 0:
|
||||||
|
print(f"\n🔄 Refreshing device list (cycle {self.cycle_count})...")
|
||||||
|
self.fetch_devices()
|
||||||
|
|
||||||
|
# Wait for next cycle
|
||||||
|
time.sleep(self.probe_interval)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(f"\n\n🛑 Health probe simulator stopped by user")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n\n❌ Unexpected error in probe simulator: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("🏥 DRONE DETECTION SYSTEM - HEALTH PROBE SIMULATOR")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
probe_failrate = os.getenv('PROBE_FAILRATE', '30')
|
||||||
|
probe_interval = os.getenv('PROBE_INTERVAL_SECONDS', '60')
|
||||||
|
api_url = os.getenv('API_BASE_URL', 'https://selfservice.cqers.com/drones/api')
|
||||||
|
|
||||||
|
print(f"📋 Configuration:")
|
||||||
|
print(f" PROBE_FAILRATE: {probe_failrate}%")
|
||||||
|
print(f" PROBE_INTERVAL_SECONDS: {probe_interval}s")
|
||||||
|
print(f" API_BASE_URL: {api_url}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
simulator = DeviceProbeSimulator()
|
||||||
|
simulator.run_continuous()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -158,24 +158,57 @@ class AlertService {
|
|||||||
model: User,
|
model: User,
|
||||||
as: 'user',
|
as: 'user',
|
||||||
where: { is_active: true }
|
where: { is_active: true }
|
||||||
}]
|
}],
|
||||||
|
order: [
|
||||||
|
// Order by priority: critical, high, medium, low
|
||||||
|
['priority', 'DESC']
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`📋 Found ${alertRules.length} active alert rules`);
|
||||||
|
|
||||||
|
// ENHANCED: Filter rules that match this detection and find the highest priority one
|
||||||
|
const matchingRules = [];
|
||||||
|
|
||||||
for (const rule of alertRules) {
|
for (const rule of alertRules) {
|
||||||
if (await this.shouldTriggerAlert(rule, detection, threatAssessment)) {
|
if (await this.shouldTriggerAlert(rule, detection, threatAssessment)) {
|
||||||
await this.triggerAlert(rule, detection, threatAssessment);
|
matchingRules.push(rule);
|
||||||
|
console.log(`✅ Rule "${rule.name}" matches (priority: ${rule.priority})`);
|
||||||
// Track active alert for potential clear notification
|
} else {
|
||||||
const alertKey = `${rule.id}-${detection.device_id}`;
|
console.log(`❌ Rule "${rule.name}" does not match conditions`);
|
||||||
this.activeAlerts.set(alertKey, {
|
|
||||||
rule,
|
|
||||||
detection,
|
|
||||||
threatAssessment,
|
|
||||||
alertTime: new Date()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchingRules.length === 0) {
|
||||||
|
console.log('📭 No alert rules match this detection');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIORITY-BASED DEDUPLICATION: Only trigger the highest priority rule
|
||||||
|
const highestPriorityRule = matchingRules[0]; // Already sorted by priority DESC
|
||||||
|
|
||||||
|
console.log(`🎯 ALERT DEDUPLICATION: ${matchingRules.length} rules matched, triggering only highest priority:`);
|
||||||
|
console.log(` Selected Rule: "${highestPriorityRule.name}" (priority: ${highestPriorityRule.priority})`);
|
||||||
|
|
||||||
|
if (matchingRules.length > 1) {
|
||||||
|
console.log(` Skipped Rules:`);
|
||||||
|
matchingRules.slice(1).forEach(rule => {
|
||||||
|
console.log(` - "${rule.name}" (priority: ${rule.priority})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger only the highest priority alert
|
||||||
|
await this.triggerAlert(highestPriorityRule, detection, threatAssessment);
|
||||||
|
|
||||||
|
// Track active alert for potential clear notification
|
||||||
|
const alertKey = `${highestPriorityRule.id}-${detection.device_id}`;
|
||||||
|
this.activeAlerts.set(alertKey, {
|
||||||
|
rule: highestPriorityRule,
|
||||||
|
detection,
|
||||||
|
threatAssessment,
|
||||||
|
alertTime: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing alert:', error);
|
console.error('Error processing alert:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -184,64 +217,84 @@ class AlertService {
|
|||||||
|
|
||||||
async shouldTriggerAlert(rule, detection, threatAssessment) {
|
async shouldTriggerAlert(rule, detection, threatAssessment) {
|
||||||
try {
|
try {
|
||||||
// CRITICAL SECURITY: Orlan drones (type 1) ALWAYS trigger alerts regardless of any other conditions
|
console.log(`🔍 Evaluating rule "${rule.name}" for detection from device ${detection.device_id}`);
|
||||||
if (detection.drone_type === 1) {
|
|
||||||
console.log(`🚨 ORLAN DRONE DETECTED - Force triggering alert for rule ${rule.name} (bypassing all filters)`);
|
// PRIORITY 1: Device-specific filter (most important)
|
||||||
|
if (rule.device_ids && rule.device_ids.length > 0) {
|
||||||
|
if (!rule.device_ids.includes(detection.device_id)) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Device ${detection.device_id} not in allowed devices [${rule.device_ids.join(', ')}]`);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log(`✅ Rule "${rule.name}": Device ${detection.device_id} is in allowed devices`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`✅ Rule "${rule.name}": No device filter (applies to all devices)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIORITY 2: Critical threats and Orlan drones (security overrides)
|
||||||
|
const isOrlanDrone = detection.drone_type === 1;
|
||||||
|
const isCriticalThreat = threatAssessment.level === 'critical';
|
||||||
|
|
||||||
|
if (isOrlanDrone) {
|
||||||
|
console.log(`🚨 ORLAN DRONE DETECTED - Rule "${rule.name}" will trigger (security override)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCriticalThreat) {
|
||||||
|
console.log(`🚨 CRITICAL THREAT DETECTED - Rule "${rule.name}" will trigger (security override)`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SECURITY ENHANCEMENT: Check threat level requirements
|
// PRIORITY 3: Threat level requirements
|
||||||
if (rule.min_threat_level) {
|
if (rule.min_threat_level) {
|
||||||
const threatLevels = { 'monitoring': 0, 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
|
const threatLevels = { 'monitoring': 0, 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
|
||||||
const requiredLevel = threatLevels[rule.min_threat_level] || 0;
|
const requiredLevel = threatLevels[rule.min_threat_level] || 0;
|
||||||
const currentLevel = threatLevels[threatAssessment.level] || 0;
|
const currentLevel = threatLevels[threatAssessment.level] || 0;
|
||||||
|
|
||||||
if (currentLevel < requiredLevel) {
|
if (currentLevel < requiredLevel) {
|
||||||
console.log(`Alert rule ${rule.name}: Threat level ${threatAssessment.level} below minimum ${rule.min_threat_level}`);
|
console.log(`❌ Rule "${rule.name}": Threat level ${threatAssessment.level} below minimum ${rule.min_threat_level}`);
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log(`✅ Rule "${rule.name}": Threat level ${threatAssessment.level} meets minimum ${rule.min_threat_level}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SECURITY ENHANCEMENT: For government/sensitive sites, always alert on critical threats
|
// PRIORITY 4: Drone type filter
|
||||||
if (threatAssessment.level === 'critical') {
|
|
||||||
console.log(`🚨 CRITICAL THREAT DETECTED - Force triggering alert for rule ${rule.name}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check device filter
|
|
||||||
if (rule.device_ids && rule.device_ids.length > 0 &&
|
|
||||||
!rule.device_ids.includes(detection.device_id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check drone type filter
|
|
||||||
if (rule.drone_types && rule.drone_types.length > 0 &&
|
if (rule.drone_types && rule.drone_types.length > 0 &&
|
||||||
!rule.drone_types.includes(detection.drone_type)) {
|
!rule.drone_types.includes(detection.drone_type)) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Drone type ${detection.drone_type} not in allowed types [${rule.drone_types.join(', ')}]`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check RSSI thresholds (enhanced for security)
|
// PRIORITY 5: RSSI thresholds (distance-based)
|
||||||
if (rule.min_rssi && detection.rssi < rule.min_rssi) {
|
if (rule.min_rssi && detection.rssi < rule.min_rssi) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": RSSI ${detection.rssi} below minimum ${rule.min_rssi}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SECURITY ENHANCEMENT: Check estimated distance
|
if (rule.max_rssi && detection.rssi > rule.max_rssi) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": RSSI ${detection.rssi} above maximum ${rule.max_rssi}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIORITY 6: Distance requirements
|
||||||
if (rule.max_distance && threatAssessment.estimatedDistance > rule.max_distance) {
|
if (rule.max_distance && threatAssessment.estimatedDistance > rule.max_distance) {
|
||||||
console.log(`Alert rule ${rule.name}: Distance ${threatAssessment.estimatedDistance}m exceeds maximum ${rule.max_distance}m`);
|
console.log(`❌ Rule "${rule.name}": Distance ${threatAssessment.estimatedDistance}m exceeds maximum ${rule.max_distance}m`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check frequency ranges
|
// PRIORITY 7: Frequency ranges
|
||||||
if (rule.frequency_ranges && rule.frequency_ranges.length > 0) {
|
if (rule.frequency_ranges && rule.frequency_ranges.length > 0) {
|
||||||
const inRange = rule.frequency_ranges.some(range =>
|
const inRange = rule.frequency_ranges.some(range =>
|
||||||
detection.freq >= range.min && detection.freq <= range.max
|
detection.freq >= range.min && detection.freq <= range.max
|
||||||
);
|
);
|
||||||
if (!inRange) {
|
if (!inRange) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Frequency ${detection.freq}MHz not in allowed ranges`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check time window and minimum detections
|
// PRIORITY 8: Time window and minimum detections
|
||||||
if (rule.min_detections > 1) {
|
if (rule.min_detections > 1) {
|
||||||
const timeWindowStart = new Date(Date.now() - rule.time_window * 1000);
|
const timeWindowStart = new Date(Date.now() - rule.time_window * 1000);
|
||||||
const recentDetections = await DroneDetection.count({
|
const recentDetections = await DroneDetection.count({
|
||||||
@@ -255,11 +308,12 @@ class AlertService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (recentDetections < rule.min_detections) {
|
if (recentDetections < rule.min_detections) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Only ${recentDetections} detections, need ${rule.min_detections}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cooldown period
|
// PRIORITY 9: Cooldown period check
|
||||||
if (rule.cooldown_period > 0) {
|
if (rule.cooldown_period > 0) {
|
||||||
const cooldownStart = new Date(Date.now() - rule.cooldown_period * 1000);
|
const cooldownStart = new Date(Date.now() - rule.cooldown_period * 1000);
|
||||||
const recentAlert = await AlertLog.findOne({
|
const recentAlert = await AlertLog.findOne({
|
||||||
@@ -281,11 +335,12 @@ class AlertService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (recentAlert) {
|
if (recentAlert) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Still in cooldown period`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check active hours and days
|
// PRIORITY 10: Active hours and days
|
||||||
if (rule.active_hours || rule.active_days) {
|
if (rule.active_hours || rule.active_days) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const currentHour = now.getHours();
|
const currentHour = now.getHours();
|
||||||
@@ -294,6 +349,7 @@ class AlertService {
|
|||||||
const currentDay = now.getDay() || 7; // Convert Sunday from 0 to 7
|
const currentDay = now.getDay() || 7; // Convert Sunday from 0 to 7
|
||||||
|
|
||||||
if (rule.active_days && !rule.active_days.includes(currentDay)) {
|
if (rule.active_days && !rule.active_days.includes(currentDay)) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Not active on day ${currentDay}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,11 +361,13 @@ class AlertService {
|
|||||||
if (startTime <= endTime) {
|
if (startTime <= endTime) {
|
||||||
// Same day range
|
// Same day range
|
||||||
if (currentTime < startTime || currentTime > endTime) {
|
if (currentTime < startTime || currentTime > endTime) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Outside active hours`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Overnight range
|
// Overnight range
|
||||||
if (currentTime < startTime && currentTime > endTime) {
|
if (currentTime < startTime && currentTime > endTime) {
|
||||||
|
console.log(`❌ Rule "${rule.name}": Outside active hours (overnight)`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,10 +375,11 @@ class AlertService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Rule "${rule.name}": All conditions met - WILL TRIGGER`);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking alert conditions:', error);
|
console.error(`Error evaluating alert rule ${rule.name}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
start-healthprobe.bat
Normal file
46
start-healthprobe.bat
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
@echo off
|
||||||
|
REM Health Probe Simulator Startup Script (Windows)
|
||||||
|
REM This script starts the health probe simulator that continuously sends heartbeats
|
||||||
|
|
||||||
|
echo 🏥 Starting Health Probe Simulator...
|
||||||
|
echo ==================================
|
||||||
|
|
||||||
|
REM Set defaults if not provided
|
||||||
|
if not defined PROBE_FAILRATE set PROBE_FAILRATE=30
|
||||||
|
if not defined PROBE_INTERVAL_SECONDS set PROBE_INTERVAL_SECONDS=60
|
||||||
|
if not defined API_BASE_URL set API_BASE_URL=https://selfservice.cqers.com/drones/api
|
||||||
|
|
||||||
|
echo 🔧 Configuration:
|
||||||
|
echo Failure Rate: %PROBE_FAILRATE%%%
|
||||||
|
echo Probe Interval: %PROBE_INTERVAL_SECONDS% seconds
|
||||||
|
echo API URL: %API_BASE_URL%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Check if running with Docker
|
||||||
|
if "%1"=="docker" (
|
||||||
|
echo 🐳 Starting with Docker Compose...
|
||||||
|
docker-compose --profile healthprobe up healthprobe
|
||||||
|
) else (
|
||||||
|
echo 💻 Running locally...
|
||||||
|
|
||||||
|
REM Check if Python is available
|
||||||
|
python --version >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ❌ Python is required but not installed
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check if requests module is available
|
||||||
|
python -c "import requests" >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo 📦 Installing requests module...
|
||||||
|
pip install requests
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Run the health probe simulator
|
||||||
|
echo 🚀 Starting health probe simulator...
|
||||||
|
python health_probe_simulator.py
|
||||||
|
)
|
||||||
|
|
||||||
|
pause
|
||||||
48
start-healthprobe.sh
Normal file
48
start-healthprobe.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Health Probe Simulator Startup Script
|
||||||
|
# This script starts the health probe simulator that continuously sends heartbeats
|
||||||
|
|
||||||
|
echo "🏥 Starting Health Probe Simulator..."
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
if [ -f .env ]; then
|
||||||
|
echo "📋 Loading environment from .env file..."
|
||||||
|
export $(cat .env | grep -v '^#' | xargs)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set defaults if not provided
|
||||||
|
export PROBE_FAILRATE=${PROBE_FAILRATE:-30}
|
||||||
|
export PROBE_INTERVAL_SECONDS=${PROBE_INTERVAL_SECONDS:-60}
|
||||||
|
export API_BASE_URL=${API_BASE_URL:-https://selfservice.cqers.com/drones/api}
|
||||||
|
|
||||||
|
echo "🔧 Configuration:"
|
||||||
|
echo " Failure Rate: ${PROBE_FAILRATE}%"
|
||||||
|
echo " Probe Interval: ${PROBE_INTERVAL_SECONDS} seconds"
|
||||||
|
echo " API URL: ${API_BASE_URL}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if running in Docker
|
||||||
|
if [ -n "$DOCKER_CONTAINER" ]; then
|
||||||
|
echo "🐳 Running in Docker container..."
|
||||||
|
# Run with Docker Compose profile
|
||||||
|
docker-compose --profile healthprobe up healthprobe
|
||||||
|
else
|
||||||
|
echo "💻 Running locally..."
|
||||||
|
# Check if Python is available
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "❌ Python 3 is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if requests module is available
|
||||||
|
if ! python3 -c "import requests" &> /dev/null; then
|
||||||
|
echo "📦 Installing requests module..."
|
||||||
|
pip3 install requests
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the health probe simulator
|
||||||
|
echo "🚀 Starting health probe simulator..."
|
||||||
|
python3 health_probe_simulator.py
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user