sikeaditya commited on
Commit
e93d13e
·
verified ·
1 Parent(s): a3224a6

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +24 -0
  2. README.md +59 -10
  3. app.py +337 -0
  4. requirements.txt +11 -0
  5. templates/index.html +1069 -0
  6. yolov8n.pt +3 -0
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ libgl1-mesa-glx \
8
+ libglib2.0-0 \
9
+ libsm6 \
10
+ libxext6 \
11
+ libxrender-dev \
12
+ ffmpeg \
13
+ && apt-get clean \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Copy requirements and install
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copy the rest of the application
21
+ COPY . .
22
+
23
+ # Command to run the application
24
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,59 @@
1
- ---
2
- title: INTRUSION1
3
- emoji: 🦀
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Intrusion Detection System
2
+
3
+ ## Overview
4
+
5
+ The Intrusion Detection System is designed to monitor environments using computer vision techniques. It can process real-time video feeds or uploaded images to detect potential intrusions and other relevant activities. The system utilizes YOLOv8, a state-of-the-art object detection model, to analyze video streams and images for detection purposes.
6
+
7
+ ## Features
8
+
9
+ - **Real-Time Video Feed**: Monitors live video from a webcam or camera for immediate detection.
10
+ - **Image Upload**: Allows users to upload images for detection.
11
+ - **Intrusion Detection**: Utilizes YOLOv8 for accurate detection of intruders and relevant objects.
12
+ - **User-Friendly Interface**: Simple and intuitive interface for selecting video or image upload options.
13
+
14
+ ## Technologies Used
15
+
16
+ - **Flask**: Web framework for building the application.
17
+ - **OpenCV**: Library for computer vision tasks.
18
+ - **YOLOv8**: Object detection model used for analyzing video and images.
19
+ - **HTML/CSS/JavaScript**: Frontend technologies for building the user interface.
20
+
21
+ ## Installation
22
+
23
+ ### Prerequisites
24
+
25
+ - Python 3.9
26
+
27
+ ### Clone the Repository
28
+
29
+ ```bash
30
+ git clone https://github.com/yourusername/intrusion-detection.git
31
+ ```
32
+
33
+
34
+ ### Install Dependencies
35
+
36
+ ```bash
37
+ pip install -r requirements.txt
38
+ ```
39
+
40
+ ### Model File
41
+
42
+ Make sure to download the YOLOv8 model file (`yolov8n.pt`) and place it in the project directory.
43
+
44
+ ## Running the Application
45
+
46
+ 1. Start the Flask server:
47
+
48
+ ```bash
49
+ python app.py
50
+ ```
51
+
52
+ 2. Open a web browser and navigate to `http://localhost:5000`.
53
+
54
+ 3. Choose between real-time video feed or image upload to detect intrusions.
55
+
56
+ ## Usage
57
+
58
+ - **Real-Time Video Feed**: Click the "Real-Time Video Feed" button to start the video stream from your camera. Use the "Play" and "Pause" buttons to control the video feed.
59
+ - **Upload Image**: Click the "Upload Image" button to select an image file from your device and get detection results.
app.py ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, Response, jsonify
2
+ import cv2
3
+ import time
4
+ import numpy as np
5
+ import threading
6
+ import requests
7
+ import os
8
+ import atexit
9
+ from twilio.rest import Client
10
+ from datetime import datetime
11
+ from dotenv import load_dotenv
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ app = Flask(__name__)
17
+
18
+ # For deployment in Hugging Face Spaces, we'll use environment variables with fallbacks
19
+ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID", "AC3988de38b87b0de231ee7704d9e6dafb")
20
+ TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "2a282eeb0a72c2a2bec9a1331d3cc803")
21
+ TWILIO_FROM_NUMBER = os.getenv("TWILIO_FROM_NUMBER", "+19046820459")
22
+ TWILIO_TO_NUMBER = os.getenv("TWILIO_TO_NUMBER", "+918999094929")
23
+ TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "7289300782:AAF0qzc38BQ1S5a4kyXj7F02kUjIswb1YDY")
24
+ TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "6186075118")
25
+ ROBOFLOW_API_KEY = os.getenv("ROBOFLOW_API_KEY", "IkQtIl5NGRTc0llwyIMo")
26
+ SITE_LOCATION = os.getenv("SITE_LOCATION", "1234 Main St, City, Country")
27
+
28
+ # Initialize webcam or use a placeholder for Hugging Face
29
+ # In HF Spaces, we'll use a dummy camera for demo purposes
30
+ try:
31
+ camera = cv2.VideoCapture(0)
32
+ if not camera.isOpened():
33
+ raise Exception("Could not open camera")
34
+ except Exception as e:
35
+ print(f"Camera error: {e}. Using demo mode.")
36
+ USE_DEMO_MODE = True
37
+ # Create a black frame as placeholder
38
+ demo_frame = np.zeros((480, 640, 3), dtype=np.uint8)
39
+
40
+ # Add text to the frame
41
+ cv2.putText(
42
+ demo_frame,
43
+ "Demo Mode - No Camera Access",
44
+ (50, 240),
45
+ cv2.FONT_HERSHEY_SIMPLEX,
46
+ 1,
47
+ (255, 255, 255),
48
+ 2
49
+ )
50
+ else:
51
+ USE_DEMO_MODE = False
52
+
53
+ # Initialize the Roboflow Inference Client
54
+ try:
55
+ from inference_sdk import InferenceHTTPClient
56
+ CLIENT = InferenceHTTPClient(
57
+ api_url="https://detect.roboflow.com",
58
+ api_key=ROBOFLOW_API_KEY
59
+ )
60
+ except ImportError:
61
+ print("Inference SDK not available. Using placeholder detection.")
62
+ CLIENT = None
63
+
64
+ # Detection settings
65
+ DETECTION_INTERVAL = 3 # seconds
66
+ ALERT_INTERVAL = 300 # seconds
67
+ last_alert_time = 0
68
+
69
+ # Cooldown for updating detection counts (in seconds)
70
+ DETECTION_COOLDOWN = 10
71
+ last_count_time = 0
72
+
73
+ # Define the classes for this project
74
+ PROJECT_CLASSES = [
75
+ "Balls", "Bird", "Cat", "Dog", "Elephant", "Pig", "Tikus",
76
+ "apple", "bean", "bunny", "cattle", "cute", "leopard", "lion",
77
+ "rat", "standpig", "tiger", "Person"
78
+ ]
79
+
80
+ # Store detection statistics
81
+ detection_counts = {cls: 0 for cls in PROJECT_CLASSES}
82
+
83
+ # Alert history
84
+ alert_history = []
85
+
86
+ def cleanup():
87
+ """Release the camera when the application exits."""
88
+ global camera
89
+ if not USE_DEMO_MODE and camera is not None and camera.isOpened():
90
+ camera.release()
91
+ print("Camera released.")
92
+
93
+ # Register cleanup function to run on exit
94
+ atexit.register(cleanup)
95
+
96
+ def make_call():
97
+ """Initiate a call using Twilio."""
98
+ try:
99
+ client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
100
+ call = client.calls.create(
101
+ url="http://demo.twilio.com/docs/voice.xml",
102
+ to=TWILIO_TO_NUMBER,
103
+ from_=TWILIO_FROM_NUMBER
104
+ )
105
+ print("Call initiated. Call SID:", call.sid)
106
+ return True
107
+ except Exception as e:
108
+ print(f"Failed to make call: {e}")
109
+ return False
110
+
111
+ def send_telegram_message(image, caption):
112
+ """Send an alert image with caption via Telegram."""
113
+ try:
114
+ send_photo_url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendPhoto"
115
+ ret, buffer = cv2.imencode('.jpg', image)
116
+ if not ret:
117
+ print("Failed to encode image.")
118
+ return False
119
+
120
+ files = {"photo": ("alert.jpg", buffer.tobytes(), "image/jpeg")}
121
+ data = {"chat_id": TELEGRAM_CHAT_ID, "caption": caption}
122
+ response = requests.post(send_photo_url, data=data, files=files)
123
+
124
+ if response.status_code == 200:
125
+ print("Telegram alert sent.")
126
+ return True
127
+ else:
128
+ print(f"Failed to send Telegram alert. Status code: {response.status_code}")
129
+ return False
130
+ except Exception as e:
131
+ print(f"Error sending Telegram message: {e}")
132
+ return False
133
+
134
+ def play_siren():
135
+ """Play a siren sound alert - this won't work in HF Spaces."""
136
+ print("Alert sound would play here (disabled in HF Spaces)")
137
+
138
+ def process_frame(frame):
139
+ """Process a frame for object detection."""
140
+ global detection_counts, last_count_time
141
+
142
+ if CLIENT is None:
143
+ # Generate demo predictions if Roboflow isn't available
144
+ predictions = [
145
+ {
146
+ 'class': 'Person',
147
+ 'confidence': 0.92,
148
+ 'x': frame.shape[1] // 2,
149
+ 'y': frame.shape[0] // 2,
150
+ 'width': 100,
151
+ 'height': 200
152
+ }
153
+ ]
154
+ detected_objects = {'Person': 1}
155
+ return predictions, detected_objects
156
+
157
+ # Save the frame temporarily for inference
158
+ image_path = "/tmp/temp_frame.jpg"
159
+ cv2.imwrite(image_path, frame)
160
+
161
+ try:
162
+ # Perform object detection using Roboflow
163
+ result = CLIENT.infer(image_path, model_id="yolov8n-640")
164
+ predictions = result.get('predictions', [])
165
+ except Exception as e:
166
+ print(f"Error during inference: {e}")
167
+ predictions = []
168
+
169
+ detected_objects = {}
170
+ current_frame_time = time.time()
171
+
172
+ # Only update detection counts if the cooldown period has passed
173
+ if current_frame_time - last_count_time >= DETECTION_COOLDOWN:
174
+ for obj in predictions:
175
+ class_name = obj['class']
176
+ # Perform case-insensitive matching
177
+ for project_class in PROJECT_CLASSES:
178
+ if class_name.lower() == project_class.lower():
179
+ detection_counts[project_class] = detection_counts.get(project_class, 0) + 1
180
+ detected_objects[project_class] = detected_objects.get(project_class, 0) + 1
181
+ break
182
+ last_count_time = current_frame_time
183
+
184
+ # Clean up temporary file
185
+ try:
186
+ if os.path.exists(image_path):
187
+ os.remove(image_path)
188
+ except Exception as e:
189
+ print(f"Failed to remove temporary file: {e}")
190
+
191
+ return predictions, detected_objects
192
+
193
+ def gen_frames():
194
+ """Video streaming with object detection."""
195
+ global last_alert_time, alert_history
196
+
197
+ while True:
198
+ if USE_DEMO_MODE:
199
+ # In demo mode, generate a dynamic demo frame
200
+ frame = demo_frame.copy()
201
+
202
+ # Add a moving element to show it's active
203
+ t = time.time()
204
+ x = int(320 + 200 * np.sin(t))
205
+ y = int(240 + 100 * np.cos(t))
206
+ cv2.circle(frame, (x, y), 20, (0, 165, 255), -1)
207
+
208
+ # Generate some random detections for demo
209
+ if time.time() % 10 < 5: # Every 5 seconds
210
+ predictions = [
211
+ {
212
+ 'class': 'Person',
213
+ 'confidence': 0.92,
214
+ 'x': x,
215
+ 'y': y,
216
+ 'width': 100,
217
+ 'height': 200
218
+ }
219
+ ]
220
+ else:
221
+ predictions = []
222
+
223
+ detected_objects = {'Person': 1} if predictions else {}
224
+ else:
225
+ # Normal camera mode
226
+ success, frame = camera.read()
227
+ if not success:
228
+ print("Failed to capture frame from camera")
229
+ time.sleep(0.1)
230
+ continue
231
+
232
+ # Process frame for object detection
233
+ predictions, detected_objects = process_frame(frame)
234
+
235
+ # Draw detections on the frame
236
+ for obj in predictions:
237
+ x, y, w, h = int(obj['x']), int(obj['y']), int(obj['width']), int(obj['height'])
238
+ class_name = obj['class']
239
+ confidence = obj['confidence']
240
+
241
+ # Use different colors based on the class (case-insensitive check)
242
+ color = (0, 255, 0) # Default green
243
+ if class_name.lower() == "person":
244
+ color = (0, 0, 255) # Red for persons
245
+
246
+ # Draw rectangle around the object
247
+ cv2.rectangle(frame, (x - w // 2, y - h // 2), (x + w // 2, y + h // 2), color, 2)
248
+
249
+ # Add a label with class name and confidence
250
+ label = f"{class_name}: {confidence:.2f}"
251
+ (text_width, text_height), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)
252
+ cv2.rectangle(frame, (x - w // 2, y - h // 2 - text_height - 5),
253
+ (x - w // 2 + text_width, y - h // 2), color, -1)
254
+ cv2.putText(frame, label, (x - w // 2, y - h // 2 - 5),
255
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
256
+
257
+ # Alert if any object from the project classes is detected and the alert interval has passed
258
+ current_time = time.time()
259
+ if detected_objects and (current_time - last_alert_time >= ALERT_INTERVAL):
260
+ # Get the current date and time
261
+ detected_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
262
+
263
+ # Create a caption listing the detected classes with timestamp and location
264
+ caption = (
265
+ f"Alert! Detected: {', '.join(detected_objects.keys())}\n"
266
+ f"Time: {detected_time}\n"
267
+ f"Location: {SITE_LOCATION}"
268
+ )
269
+
270
+ # Add to alert history
271
+ alert_info = {
272
+ "time": detected_time,
273
+ "objects": list(detected_objects.keys()),
274
+ "counts": detected_objects
275
+ }
276
+ alert_history.append(alert_info)
277
+
278
+ # Keep only the last 10 alerts
279
+ if len(alert_history) > 10:
280
+ alert_history.pop(0)
281
+
282
+ # In a real environment, we would start alert threads
283
+ # In HF Spaces, we'll just log the alerts
284
+ print(f"Alert triggered: {caption}")
285
+
286
+ last_alert_time = current_time
287
+
288
+ # Add timestamp to frame
289
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
290
+ cv2.putText(frame, timestamp, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
291
+
292
+ # Encode the frame for streaming
293
+ ret, buffer = cv2.imencode('.jpg', frame)
294
+ if not ret:
295
+ continue
296
+
297
+ yield (b'--frame\r\n'
298
+ b'Content-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
299
+
300
+ # Add a small delay to control frame rate
301
+ time.sleep(0.05)
302
+
303
+ @app.route('/')
304
+ def index():
305
+ return render_template('index.html')
306
+
307
+ @app.route('/video_feed')
308
+ def video_feed():
309
+ return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
310
+
311
+ @app.route('/detection_data')
312
+ def detection_data():
313
+ """Return the current detection counts as JSON."""
314
+ filtered_counts = {k: v for k, v in detection_counts.items() if v > 0}
315
+ return jsonify(filtered_counts)
316
+
317
+ @app.route('/alert_history')
318
+ def get_alert_history():
319
+ """Return the history of alerts as JSON."""
320
+ return jsonify(alert_history)
321
+
322
+ @app.route('/reset_counts')
323
+ def reset_counts():
324
+ """Reset all detection counts."""
325
+ global detection_counts
326
+ detection_counts = {cls: 0 for cls in PROJECT_CLASSES}
327
+ return jsonify({"status": "success", "message": "Detection counts reset"})
328
+
329
+ # Add a dummy route for Hugging Face Spaces healthcheck
330
+ @app.route('/healthcheck')
331
+ def healthcheck():
332
+ return jsonify({"status": "healthy"})
333
+
334
+ if __name__ == '__main__':
335
+ # Get port from environment (needed for Hugging Face Spaces)
336
+ port = int(os.environ.get('PORT', 7860))
337
+ app.run(host='0.0.0.0', port=port)
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask
2
+ ultralytics
3
+ opencv-python
4
+ Pillow
5
+ opencv-python-headless==4.7.0.72
6
+ numpy==1.23.5
7
+ requests==2.28.2
8
+ twilio==7.16.4
9
+ playsound==1.3.0
10
+ inference_sdk
11
+ python-dotenv==1.0.0
templates/index.html ADDED
@@ -0,0 +1,1069 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Security Monitoring System</title>
7
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
8
+ <meta http-equiv="Pragma" content="no-cache" />
9
+ <meta http-equiv="Expires" content="0" />
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ :root {
13
+ --primary-color: #2563eb;
14
+ --primary-dark: #1d4ed8;
15
+ --primary-light: #3b82f6;
16
+ --secondary-color: #10b981;
17
+ --danger-color: #ef4444;
18
+ --warning-color: #f59e0b;
19
+ --neutral-dark: #171717;
20
+ --neutral: #262626;
21
+ --neutral-light: #404040;
22
+ --background-color: #f9fafb;
23
+ --card-color: #ffffff;
24
+ --text-color: #111827;
25
+ --text-secondary: #6b7280;
26
+ --text-light: #9ca3af;
27
+ --border-color: #e5e7eb;
28
+ --shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
29
+ --border-radius: 0.5rem;
30
+ }
31
+
32
+ /* Dark mode */
33
+ .dark-mode {
34
+ --primary-color: #3b82f6;
35
+ --primary-dark: #2563eb;
36
+ --primary-light: #60a5fa;
37
+ --secondary-color: #10b981;
38
+ --background-color: #0f172a;
39
+ --card-color: #1e293b;
40
+ --text-color: #f9fafb;
41
+ --text-secondary: #cbd5e1;
42
+ --text-light: #94a3b8;
43
+ --border-color: #334155;
44
+ --neutral-dark: #f9fafb;
45
+ --neutral: #e5e7eb;
46
+ --neutral-light: #d1d5db;
47
+ }
48
+
49
+ * {
50
+ margin: 0;
51
+ padding: 0;
52
+ box-sizing: border-box;
53
+ transition: background-color 0.3s, color 0.3s;
54
+ }
55
+
56
+ body {
57
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
58
+ background-color: var(--background-color);
59
+ color: var(--text-color);
60
+ display: flex;
61
+ flex-direction: column;
62
+ min-height: 100vh;
63
+ position: relative;
64
+ }
65
+
66
+ .header {
67
+ background-color: var(--card-color);
68
+ color: var(--text-color);
69
+ padding: 1rem 2rem;
70
+ display: flex;
71
+ justify-content: space-between;
72
+ align-items: center;
73
+ box-shadow: var(--shadow);
74
+ border-bottom: 1px solid var(--border-color);
75
+ position: sticky;
76
+ top: 0;
77
+ z-index: 100;
78
+ }
79
+
80
+ .header-title {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 0.75rem;
84
+ }
85
+
86
+ .header-title i {
87
+ color: var(--primary-color);
88
+ }
89
+
90
+ .header h1 {
91
+ font-size: 1.25rem;
92
+ margin: 0;
93
+ font-weight: 600;
94
+ }
95
+
96
+ .header-actions {
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 1.5rem;
100
+ }
101
+
102
+ .header-actions div {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 0.5rem;
106
+ }
107
+
108
+ .header-actions i {
109
+ font-size: 1.25rem;
110
+ color: var(--primary-color);
111
+ }
112
+
113
+ .container {
114
+ display: flex;
115
+ flex: 1;
116
+ position: relative;
117
+ }
118
+
119
+ .sidebar {
120
+ width: 320px;
121
+ background-color: var(--card-color);
122
+ border-right: 1px solid var(--border-color);
123
+ padding: 1.5rem;
124
+ overflow-y: auto;
125
+ transition: all 0.3s ease;
126
+ height: calc(100vh - 64px);
127
+ position: sticky;
128
+ top: 64px;
129
+ }
130
+
131
+ .sidebar-header {
132
+ display: flex;
133
+ justify-content: space-between;
134
+ align-items: center;
135
+ margin-bottom: 1.5rem;
136
+ padding-bottom: 0.75rem;
137
+ border-bottom: 2px solid var(--primary-color);
138
+ }
139
+
140
+ .main-content {
141
+ flex: 1;
142
+ padding: 1.5rem;
143
+ overflow-y: auto;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 1.5rem;
147
+ }
148
+
149
+ .dashboard-grid {
150
+ display: grid;
151
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
152
+ gap: 1.5rem;
153
+ }
154
+
155
+ .section-title {
156
+ color: var(--text-color);
157
+ font-size: 1.1rem;
158
+ margin-bottom: 1rem;
159
+ padding-bottom: 0.5rem;
160
+ border-bottom: 2px solid var(--primary-color);
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: space-between;
164
+ }
165
+
166
+ .section-title button {
167
+ background: none;
168
+ border: none;
169
+ color: var(--primary-color);
170
+ cursor: pointer;
171
+ font-size: 0.9rem;
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 0.25rem;
175
+ padding: 0.25rem 0.5rem;
176
+ border-radius: var(--border-radius);
177
+ }
178
+
179
+ .section-title button:hover {
180
+ background-color: rgba(59, 130, 246, 0.1);
181
+ }
182
+
183
+ .card {
184
+ background-color: var(--card-color);
185
+ border-radius: var(--border-radius);
186
+ box-shadow: var(--shadow);
187
+ padding: 1.5rem;
188
+ margin-bottom: 1.5rem;
189
+ border-top: 4px solid var(--primary-color);
190
+ }
191
+
192
+ .video-container {
193
+ position: relative;
194
+ overflow: hidden;
195
+ border-radius: var(--border-radius);
196
+ background-color: #000;
197
+ box-shadow: var(--shadow);
198
+ aspect-ratio: 16/9;
199
+ }
200
+
201
+ .video-feed {
202
+ width: 100%;
203
+ height: 100%;
204
+ object-fit: cover;
205
+ border-radius: var(--border-radius);
206
+ display: block;
207
+ border: none;
208
+ }
209
+
210
+ .status {
211
+ position: absolute;
212
+ top: 15px;
213
+ right: 15px;
214
+ background-color: rgba(0, 0, 0, 0.6);
215
+ color: white;
216
+ padding: 0.5rem 0.75rem;
217
+ border-radius: 20px;
218
+ font-size: 0.9rem;
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ }
223
+
224
+ .status-dot {
225
+ height: 10px;
226
+ width: 10px;
227
+ background-color: var(--primary-color);
228
+ border-radius: 50%;
229
+ display: inline-block;
230
+ animation: pulse 1.5s infinite;
231
+ }
232
+
233
+ .feed-controls {
234
+ display: flex;
235
+ justify-content: space-between;
236
+ margin-top: 1rem;
237
+ }
238
+
239
+ .camera-controls {
240
+ display: flex;
241
+ gap: 1rem;
242
+ }
243
+
244
+ .control-btn {
245
+ background-color: var(--card-color);
246
+ color: var(--text-color);
247
+ border: 1px solid var(--border-color);
248
+ border-radius: var(--border-radius);
249
+ padding: 0.5rem 1rem;
250
+ display: flex;
251
+ align-items: center;
252
+ gap: 0.5rem;
253
+ cursor: pointer;
254
+ transition: all 0.2s ease;
255
+ font-size: 0.9rem;
256
+ }
257
+
258
+ .control-btn:hover {
259
+ background-color: var(--primary-color);
260
+ color: white;
261
+ }
262
+
263
+ .alert-btn {
264
+ background-color: var(--danger-color);
265
+ color: white;
266
+ border: none;
267
+ }
268
+
269
+ .alert-btn:hover {
270
+ background-color: #dc2626;
271
+ }
272
+
273
+ @keyframes pulse {
274
+ 0% { opacity: 1; }
275
+ 50% { opacity: 0.5; }
276
+ 100% { opacity: 1; }
277
+ }
278
+
279
+ .detection-list {
280
+ list-style: none;
281
+ margin-top: 0.5rem;
282
+ max-height: 300px;
283
+ overflow-y: auto;
284
+ }
285
+
286
+ .detection-item {
287
+ display: flex;
288
+ justify-content: space-between;
289
+ padding: 0.75rem;
290
+ border-bottom: 1px solid var(--border-color);
291
+ transition: background-color 0.2s ease;
292
+ align-items: center;
293
+ }
294
+
295
+ .detection-item:hover {
296
+ background-color: rgba(59, 130, 246, 0.1);
297
+ }
298
+
299
+ .detection-item:last-child {
300
+ border-bottom: none;
301
+ }
302
+
303
+ .detection-label {
304
+ display: flex;
305
+ align-items: center;
306
+ gap: 0.5rem;
307
+ }
308
+
309
+ .detection-icon {
310
+ width: 24px;
311
+ height: 24px;
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ }
316
+
317
+ .detection-count {
318
+ background-color: var(--primary-color);
319
+ color: white;
320
+ padding: 0.25rem 0.5rem;
321
+ border-radius: 12px;
322
+ font-size: 0.8rem;
323
+ min-width: 2rem;
324
+ text-align: center;
325
+ }
326
+
327
+ .alert-info {
328
+ background-color: rgba(59, 130, 246, 0.1);
329
+ border-left: 4px solid var(--primary-color);
330
+ padding: 1rem;
331
+ margin-top: 1rem;
332
+ border-radius: 4px;
333
+ display: flex;
334
+ align-items: center;
335
+ gap: 10px;
336
+ }
337
+
338
+ .alert-info i {
339
+ color: var(--primary-color);
340
+ font-size: 1.2rem;
341
+ }
342
+
343
+ .alert-history {
344
+ max-height: 300px;
345
+ overflow-y: auto;
346
+ }
347
+
348
+ .alert-card {
349
+ padding: 1rem;
350
+ border-radius: var(--border-radius);
351
+ margin-bottom: 1rem;
352
+ border-left: 4px solid var(--danger-color);
353
+ background-color: rgba(239, 68, 68, 0.1);
354
+ }
355
+
356
+ .alert-card:last-child {
357
+ margin-bottom: 0;
358
+ }
359
+
360
+ .alert-header {
361
+ display: flex;
362
+ justify-content: space-between;
363
+ margin-bottom: 0.5rem;
364
+ font-weight: 500;
365
+ color: var(--danger-color);
366
+ }
367
+
368
+ .alert-details {
369
+ display: flex;
370
+ gap: 0.5rem;
371
+ flex-wrap: wrap;
372
+ }
373
+
374
+ .alert-tag {
375
+ background-color: var(--danger-color);
376
+ color: white;
377
+ border-radius: 12px;
378
+ padding: 0.25rem 0.75rem;
379
+ font-size: 0.8rem;
380
+ }
381
+
382
+ #graph-container {
383
+ height: 300px;
384
+ margin-top: 1rem;
385
+ }
386
+
387
+ /* Stats cards */
388
+ .stats-container {
389
+ display: grid;
390
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
391
+ gap: 1rem;
392
+ margin-bottom: 1.5rem;
393
+ }
394
+
395
+ .stat-card {
396
+ background-color: var(--card-color);
397
+ border-radius: var(--border-radius);
398
+ padding: 1.25rem;
399
+ box-shadow: var(--shadow);
400
+ display: flex;
401
+ flex-direction: column;
402
+ gap: 0.5rem;
403
+ }
404
+
405
+ .stat-title {
406
+ color: var(--text-secondary);
407
+ font-size: 0.9rem;
408
+ }
409
+
410
+ .stat-value {
411
+ font-size: 1.5rem;
412
+ font-weight: 600;
413
+ color: var(--text-color);
414
+ }
415
+
416
+ .stat-comparison {
417
+ font-size: 0.85rem;
418
+ display: flex;
419
+ align-items: center;
420
+ gap: 0.25rem;
421
+ }
422
+
423
+ .stat-up {
424
+ color: var(--secondary-color);
425
+ }
426
+
427
+ .stat-down {
428
+ color: var(--danger-color);
429
+ }
430
+
431
+ /* Switch */
432
+ .switch {
433
+ position: relative;
434
+ display: inline-block;
435
+ width: 48px;
436
+ height: 24px;
437
+ }
438
+
439
+ .switch input {
440
+ opacity: 0;
441
+ width: 0;
442
+ height: 0;
443
+ }
444
+
445
+ .slider {
446
+ position: absolute;
447
+ cursor: pointer;
448
+ top: 0;
449
+ left: 0;
450
+ right: 0;
451
+ bottom: 0;
452
+ background-color: var(--border-color);
453
+ -webkit-transition: .4s;
454
+ transition: .4s;
455
+ border-radius: 24px;
456
+ }
457
+
458
+ .slider:before {
459
+ position: absolute;
460
+ content: "";
461
+ height: 18px;
462
+ width: 18px;
463
+ left: 3px;
464
+ bottom: 3px;
465
+ background-color: white;
466
+ -webkit-transition: .4s;
467
+ transition: .4s;
468
+ border-radius: 50%;
469
+ }
470
+
471
+ input:checked + .slider {
472
+ background-color: var(--primary-color);
473
+ }
474
+
475
+ input:focus + .slider {
476
+ box-shadow: 0 0 1px var(--primary-color);
477
+ }
478
+
479
+ input:checked + .slider:before {
480
+ -webkit-transform: translateX(24px);
481
+ -ms-transform: translateX(24px);
482
+ transform: translateX(24px);
483
+ }
484
+
485
+ /* Responsive */
486
+ @media (max-width: 1024px) {
487
+ .container {
488
+ flex-direction: column;
489
+ }
490
+ .sidebar {
491
+ width: 100%;
492
+ height: auto;
493
+ position: relative;
494
+ top: 0;
495
+ border-right: none;
496
+ border-bottom: 1px solid var(--border-color);
497
+ }
498
+ .main-content {
499
+ padding: 1rem;
500
+ }
501
+ }
502
+
503
+ @media (max-width: 768px) {
504
+ .header {
505
+ padding: 1rem;
506
+ flex-direction: column;
507
+ align-items: flex-start;
508
+ gap: 0.5rem;
509
+ }
510
+ .header-actions {
511
+ width: 100%;
512
+ justify-content: space-between;
513
+ }
514
+ .dashboard-grid {
515
+ grid-template-columns: 1fr;
516
+ }
517
+ .stats-container {
518
+ grid-template-columns: 1fr 1fr;
519
+ }
520
+ }
521
+
522
+ @media (max-width: 576px) {
523
+ .stats-container {
524
+ grid-template-columns: 1fr;
525
+ }
526
+ .camera-controls {
527
+ flex-wrap: wrap;
528
+ }
529
+ .feed-controls {
530
+ flex-direction: column;
531
+ gap: 1rem;
532
+ }
533
+ }
534
+ </style>
535
+ </head>
536
+ <body>
537
+ <header class="header">
538
+ <div class="header-title">
539
+ <i class="fas fa-shield-alt"></i>
540
+ <h1>Advanced Security Monitoring System</h1>
541
+ </div>
542
+ <div class="header-actions">
543
+ <div>
544
+ <i class="fas fa-clock"></i>
545
+ <span id="current-time"></span>
546
+ </div>
547
+ <div>
548
+ <i class="fas fa-moon"></i>
549
+ <label class="switch">
550
+ <input type="checkbox" id="dark-mode-toggle">
551
+ <span class="slider"></span>
552
+ </label>
553
+ </div>
554
+ </div>
555
+ </header>
556
+
557
+ <div class="container">
558
+ <div class="sidebar">
559
+ <div class="stats-container">
560
+ <div class="stat-card" style="border-left: 4px solid var(--primary-color);">
561
+ <div class="stat-title">Total Detections</div>
562
+ <div class="stat-value" id="total-detections">0</div>
563
+ <div class="stat-comparison stat-up">
564
+ <i class="fas fa-arrow-up"></i>
565
+ <span id="detection-rate">Calculating...</span>
566
+ </div>
567
+ </div>
568
+ <div class="stat-card" style="border-left: 4px solid var(--danger-color);">
569
+ <div class="stat-title">Alerts Today</div>
570
+ <div class="stat-value" id="alerts-today">0</div>
571
+ <div class="stat-comparison">
572
+ <span id="last-alert">No alerts yet</span>
573
+ </div>
574
+ </div>
575
+ </div>
576
+
577
+ <div class="card">
578
+ <div class="section-title">
579
+ <span>Detected Objects</span>
580
+ <button id="reset-counts">
581
+ <i class="fas fa-redo-alt"></i>
582
+ Reset
583
+ </button>
584
+ </div>
585
+ <ul id="class-list" class="detection-list">
586
+ <li class="detection-item">Loading data...</li>
587
+ </ul>
588
+ </div>
589
+
590
+ <div class="card">
591
+ <div class="section-title">Detection Trend</div>
592
+ <div id="graph-container"></div>
593
+ </div>
594
+
595
+ <div class="card">
596
+ <div class="section-title">Recent Alerts</div>
597
+ <div id="alert-history" class="alert-history">
598
+ <div class="alert-card">
599
+ <div class="alert-header">
600
+ <span>Loading alerts...</span>
601
+ </div>
602
+ </div>
603
+ </div>
604
+ </div>
605
+ </div>
606
+
607
+ <div class="main-content">
608
+ <div class="card">
609
+ <div class="section-title">Live Camera Feed</div>
610
+ <div class="video-container">
611
+ <img id="video-feed" class="video-feed" src="{{ url_for('video_feed') }}" alt="Live Video Feed">
612
+ <div class="status">
613
+ <span class="status-dot"></span>
614
+ Live Monitoring
615
+ </div>
616
+ </div>
617
+ <div class="feed-controls">
618
+ <div class="camera-controls">
619
+ <button class="control-btn">
620
+ <i class="fas fa-sync-alt"></i>
621
+ Refresh
622
+ </button>
623
+ <button class="control-btn">
624
+ <i class="fas fa-camera"></i>
625
+ Snapshot
626
+ </button>
627
+ <button class="control-btn">
628
+ <i class="fas fa-expand"></i>
629
+ Fullscreen
630
+ </button>
631
+ </div>
632
+ <button class="control-btn alert-btn">
633
+ <i class="fas fa-exclamation-triangle"></i>
634
+ Test Alert
635
+ </button>
636
+ </div>
637
+ <div class="alert-info">
638
+ <i class="fas fa-bell"></i>
639
+ <p>Automatic alerts will be sent if security threats are detected. The system will make a phone call and send notifications via Telegram.</p>
640
+ </div>
641
+ </div>
642
+
643
+ <div class="dashboard-grid">
644
+ <div class="card">
645
+ <div class="section-title">Detection Distribution</div>
646
+ <div id="pie-chart" style="height: 300px;"></div>
647
+ </div>
648
+ <div class="card">
649
+ <div class="section-title">Alert Configuration</div>
650
+ <form id="alert-config">
651
+ <div style="margin-bottom: 1rem;">
652
+ <label style="display: block; margin-bottom: 0.5rem;">Detection Threshold</label>
653
+ <input type="range" min="0" max="100" value="50" id="detection-threshold" style="width: 100%;">
654
+ <div style="display: flex; justify-content: space-between; margin-top: 0.25rem;">
655
+ <span>Low</span>
656
+ <span>High</span>
657
+ </div>
658
+ </div>
659
+
660
+ <div style="margin-bottom: 1rem;">
661
+ <label style="display: block; margin-bottom: 0.5rem;">Alert Interval (seconds)</label>
662
+ <input type="number" min="30" max="600" value="300" id="alert-interval" style="width: 100%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: var(--border-radius);">
663
+ </div>
664
+
665
+ <div style="margin-bottom: 1rem;">
666
+ <label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
667
+ <input type="checkbox" id="enable-calls" checked>
668
+ Enable Phone Calls
669
+ </label>
670
+
671
+ <label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
672
+ <input type="checkbox" id="enable-telegram" checked>
673
+ Enable Telegram
674
+ </label>
675
+
676
+ <label style="display: flex; align-items: center; gap: 0.5rem;">
677
+ <input type="checkbox" id="enable-sounds" checked>
678
+ Enable Sound Alerts
679
+ </label>
680
+ </div>
681
+
682
+ <button type="submit" class="control-btn" style="width: 100%; background-color: var(--primary-color); color: white;">
683
+ <i class="fas fa-save"></i>
684
+ Save Configuration
685
+ </button>
686
+ </form>
687
+ </div>
688
+ </div>
689
+ </div>
690
+ </div>
691
+
692
+ <!-- Load the Charts.js library -->
693
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
694
+
695
+ <script>
696
+ // Dark mode toggle
697
+ const darkModeToggle = document.getElementById('dark-mode-toggle');
698
+ const body = document.body;
699
+
700
+ // Check for saved dark mode preference
701
+ if (localStorage.getItem('darkMode') === 'enabled') {
702
+ body.classList.add('dark-mode');
703
+ darkModeToggle.checked = true;
704
+ }
705
+
706
+ darkModeToggle.addEventListener('change', () => {
707
+ if (darkModeToggle.checked) {
708
+ body.classList.add('dark-mode');
709
+ localStorage.setItem('darkMode', 'enabled');
710
+ } else {
711
+ body.classList.remove('dark-mode');
712
+ localStorage.setItem('darkMode', null);
713
+ }
714
+ });
715
+
716
+ // Current time display
717
+ function updateTime() {
718
+ const now = new Date();
719
+ const timeElement = document.getElementById('current-time');
720
+ timeElement.textContent = now.toLocaleTimeString();
721
+ }
722
+
723
+ // Update time every second
724
+ setInterval(updateTime, 1000);
725
+ updateTime(); // Initial call
726
+
727
+ // Tracking detection counts and history
728
+ let detectionCounts = {};
729
+ let alertHistory = [];
730
+ let totalDetections = 0;
731
+ const detectionData = [];
732
+ const chartLabels = [];
733
+
734
+ // Generate some initial data for the trend chart
735
+ for (let i = 0; i < 10; i++) {
736
+ const date = new Date();
737
+ date.setMinutes(date.getMinutes() - (9 - i));
738
+ chartLabels.push(date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}));
739
+ detectionData.push(0);
740
+ }
741
+
742
+ // Create trend chart
743
+ const trendCtx = document.createElement('canvas');
744
+ document.getElementById('graph-container').appendChild(trendCtx);
745
+
746
+ const trendChart = new Chart(trendCtx, {
747
+ type: 'line',
748
+ data: {
749
+ labels: chartLabels,
750
+ datasets: [{
751
+ label: 'Detections',
752
+ data: detectionData,
753
+ backgroundColor: 'rgba(59, 130, 246, 0.2)',
754
+ borderColor: 'rgba(59, 130, 246, 1)',
755
+ borderWidth: 2,
756
+ tension: 0.3,
757
+ fill: true
758
+ }]
759
+ },
760
+ options: {
761
+ responsive: true,
762
+ maintainAspectRatio: false,
763
+ plugins: {
764
+ legend: {
765
+ display: false
766
+ }
767
+ },
768
+ scales: {
769
+ y: {
770
+ beginAtZero: true,
771
+ ticks: {
772
+ stepSize: 1
773
+ }
774
+ }
775
+ }
776
+ }
777
+ });
778
+
779
+ // Create pie chart for detection distribution
780
+ const pieCtx = document.createElement('canvas');
781
+ document.getElementById('pie-chart').appendChild(pieCtx);
782
+
783
+ const pieChart = new Chart(pieCtx, {
784
+ type: 'doughnut',
785
+ data: {
786
+ labels: [],
787
+ datasets: [{
788
+ data: [],
789
+ backgroundColor: [
790
+ 'rgba(59, 130, 246, 0.8)',
791
+ 'rgba(16, 185, 129, 0.8)',
792
+ 'rgba(239, 68, 68, 0.8)',
793
+ 'rgba(245, 158, 11, 0.8)',
794
+ 'rgba(139, 92, 246, 0.8)',
795
+ 'rgba(236, 72, 153, 0.8)',
796
+ 'rgba(20, 184, 166, 0.8)',
797
+ 'rgba(249, 115, 22, 0.8)',
798
+ 'rgba(168, 85, 247, 0.8)',
799
+ 'rgba(217, 70, 239, 0.8)'
800
+ ],
801
+ borderWidth: 1
802
+ }]
803
+ },
804
+ options: {
805
+ responsive: true,
806
+ maintainAspectRatio: false,
807
+ plugins: {
808
+ legend: {
809
+ position: 'right'
810
+ }
811
+ }
812
+ }
813
+ });
814
+
815
+ // Function to update the detection list display
816
+ function updateDetectionList() {
817
+ const list = document.getElementById('class-list');
818
+ list.innerHTML = '';
819
+
820
+ // Sort by count (highest first)
821
+ const sortedClasses = Object.keys(detectionCounts).sort((a, b) =>
822
+ detectionCounts[b] - detectionCounts[a]
823
+ );
824
+
825
+ if (sortedClasses.length === 0) {
826
+ const listItem = document.createElement('li');
827
+ listItem.className = 'detection-item';
828
+ listItem.textContent = 'No detections yet';
829
+ list.appendChild(listItem);
830
+ return;
831
+ }
832
+
833
+ sortedClasses.forEach(className => {
834
+ const count = detectionCounts[className];
835
+
836
+ const listItem = document.createElement('li');
837
+ listItem.className = 'detection-item';
838
+
839
+ const label = document.createElement('div');
840
+ label.className = 'detection-label';
841
+
842
+ const icon = document.createElement('div');
843
+ icon.className = 'detection-icon';
844
+
845
+ // Choose appropriate icon based on class name
846
+ let iconClass = 'fas fa-box';
847
+ if (className.toLowerCase().includes('person')) {
848
+ iconClass = 'fas fa-user';
849
+ } else if (className.toLowerCase().includes('dog') ||
850
+ className.toLowerCase().includes('cat') ||
851
+ className.toLowerCase().includes('animal') ||
852
+ className.toLowerCase().includes('bird')) {
853
+ iconClass = 'fas fa-paw';
854
+ } else if (className.toLowerCase().includes('car') ||
855
+ className.toLowerCase().includes('truck') ||
856
+ className.toLowerCase().includes('vehicle')) {
857
+ iconClass = 'fas fa-car';
858
+ }
859
+
860
+ const iconElement = document.createElement('i');
861
+ iconElement.className = iconClass;
862
+ icon.appendChild(iconElement);
863
+
864
+ const nameSpan = document.createElement('span');
865
+ nameSpan.textContent = className;
866
+
867
+ label.appendChild(icon);
868
+ label.appendChild(nameSpan);
869
+
870
+ const countElement = document.createElement('div');
871
+ countElement.className = 'detection-count';
872
+ countElement.textContent = count;
873
+
874
+ listItem.appendChild(label);
875
+ listItem.appendChild(countElement);
876
+ list.appendChild(listItem);
877
+ });
878
+
879
+ // Update pie chart
880
+ pieChart.data.labels = sortedClasses;
881
+ pieChart.data.datasets[0].data = sortedClasses.map(cls => detectionCounts[cls]);
882
+ pieChart.update();
883
+ }
884
+
885
+ // Function to update alert history display
886
+ function updateAlertHistory() {
887
+ const alertContainer = document.getElementById('alert-history');
888
+ alertContainer.innerHTML = '';
889
+
890
+ if (alertHistory.length === 0) {
891
+ const alertCard = document.createElement('div');
892
+ alertCard.className = 'alert-card';
893
+ alertCard.innerHTML = `
894
+ <div class="alert-header">
895
+ <span>No alerts yet</span>
896
+ </div>
897
+ <p>Alert history will appear here when security events are detected.</p>
898
+ `;
899
+ alertContainer.appendChild(alertCard);
900
+ return;
901
+ }
902
+
903
+ // Display latest alerts first
904
+ alertHistory.slice().reverse().forEach(alert => {
905
+ const alertCard = document.createElement('div');
906
+ alertCard.className = 'alert-card';
907
+
908
+ const alertHeader = document.createElement('div');
909
+ alertHeader.className = 'alert-header';
910
+
911
+ const alertTime = document.createElement('span');
912
+ alertTime.textContent = alert.time;
913
+
914
+ alertHeader.appendChild(alertTime);
915
+
916
+ const alertDetails = document.createElement('div');
917
+ alertDetails.className = 'alert-details';
918
+
919
+ alert.objects.forEach(obj => {
920
+ const alertTag = document.createElement('span');
921
+ alertTag.className = 'alert-tag';
922
+ alertTag.textContent = obj;
923
+ alertDetails.appendChild(alertTag);
924
+ });
925
+
926
+ alertCard.appendChild(alertHeader);
927
+ alertCard.appendChild(alertDetails);
928
+ alertContainer.appendChild(alertCard);
929
+ });
930
+
931
+ // Update alerts today count
932
+ document.getElementById('alerts-today').textContent = alertHistory.length;
933
+
934
+ // Update last alert time if there are alerts
935
+ if (alertHistory.length > 0) {
936
+ const lastAlert = alertHistory[alertHistory.length - 1];
937
+ document.getElementById('last-alert').textContent = 'Last: ' + lastAlert.time.split(' ')[1];
938
+ }
939
+ }
940
+
941
+ // Function to fetch detection data
942
+ async function fetchDetectionData() {
943
+ try {
944
+ const response = await fetch('/detection_data');
945
+ if (response.ok) {
946
+ const data = await response.json();
947
+ detectionCounts = data;
948
+
949
+ totalDetections = Object.values(detectionCounts).reduce((sum, count) => sum + count, 0);
950
+ document.getElementById('total-detections').textContent = totalDetections;
951
+
952
+ // Add new data point for trend chart
953
+ const now = new Date();
954
+ chartLabels.push(now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}));
955
+ chartLabels.shift();
956
+
957
+ // Get the latest minute's detections
958
+ const latestDetections = Object.values(detectionCounts).reduce((sum, count) => sum + count, 0) -
959
+ detectionData.reduce((sum, count) => sum + count, 0);
960
+
961
+ detectionData.push(latestDetections > 0 ? latestDetections : 0);
962
+ detectionData.shift();
963
+
964
+ trendChart.update();
965
+
966
+ // Calculate detection rate
967
+ const detectionRate = document.getElementById('detection-rate');
968
+ if (detectionData.slice(-3).some(val => val > 0)) {
969
+ detectionRate.textContent = 'Active detections';
970
+ detectionRate.parentElement.className = 'stat-comparison stat-up';
971
+ detectionRate.previousElementSibling.className = 'fas fa-arrow-up';
972
+ } else {
973
+ detectionRate.textContent = 'No recent activity';
974
+ detectionRate.parentElement.className = 'stat-comparison stat-down';
975
+ detectionRate.previousElementSibling.className = 'fas fa-arrow-down';
976
+ }
977
+
978
+ updateDetectionList();
979
+ }
980
+ } catch (error) {
981
+ console.error('Error fetching detection data:', error);
982
+ }
983
+ }
984
+
985
+ // Function to fetch alert history
986
+ async function fetchAlertHistory() {
987
+ try {
988
+ const response = await fetch('/alert_history');
989
+ if (response.ok) {
990
+ alertHistory = await response.json();
991
+ updateAlertHistory();
992
+ }
993
+ } catch (error) {
994
+ console.error('Error fetching alert history:', error);
995
+ }
996
+ }
997
+
998
+ // Reset detection counts
999
+ document.getElementById('reset-counts').addEventListener('click', async () => {
1000
+ try {
1001
+ const response = await fetch('/reset_counts');
1002
+ if (response.ok) {
1003
+ detectionCounts = {};
1004
+ totalDetections = 0;
1005
+ document.getElementById('total-detections').textContent = totalDetections;
1006
+ updateDetectionList();
1007
+
1008
+ // Reset trend chart
1009
+ detectionData.fill(0);
1010
+ trendChart.update();
1011
+
1012
+ // Reset pie chart
1013
+ pieChart.data.labels = [];
1014
+ pieChart.data.datasets[0].data = [];
1015
+ pieChart.update();
1016
+ }
1017
+ } catch (error) {
1018
+ console.error('Error resetting detection counts:', error);
1019
+ }
1020
+ });
1021
+
1022
+ // Test alert button
1023
+ document.querySelector('.alert-btn').addEventListener('click', () => {
1024
+ const testAlert = {
1025
+ time: new Date().toLocaleString(),
1026
+ objects: ['Test Alert'],
1027
+ counts: { 'Test Alert': 1 }
1028
+ };
1029
+ alertHistory.push(testAlert);
1030
+ if (alertHistory.length > 10) {
1031
+ alertHistory.shift();
1032
+ }
1033
+ updateAlertHistory();
1034
+ });
1035
+
1036
+ // Handle alert configuration form submission
1037
+ document.getElementById('alert-config').addEventListener('submit', (e) => {
1038
+ e.preventDefault();
1039
+ const threshold = document.getElementById('detection-threshold').value;
1040
+ const interval = document.getElementById('alert-interval').value;
1041
+ const enableCalls = document.getElementById('enable-calls').checked;
1042
+ const enableTelegram = document.getElementById('enable-telegram').checked;
1043
+ const enableSounds = document.getElementById('enable-sounds').checked;
1044
+
1045
+ // In a real application, this would send the configuration to the server
1046
+ const config = {
1047
+ threshold,
1048
+ interval,
1049
+ enableCalls,
1050
+ enableTelegram,
1051
+ enableSounds
1052
+ };
1053
+
1054
+ console.log('Alert configuration saved:', config);
1055
+
1056
+ // Show a confirmation message
1057
+ alert('Alert configuration saved successfully!');
1058
+ });
1059
+
1060
+ // Refresh data every 5 seconds
1061
+ setInterval(fetchDetectionData, 5000);
1062
+ setInterval(fetchAlertHistory, 10000);
1063
+
1064
+ // Initial data fetch
1065
+ fetchDetectionData();
1066
+ fetchAlertHistory();
1067
+ </script>
1068
+ </body>
1069
+ </html>
yolov8n.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f59b3d833e2ff32e194b5bb8e08d211dc7c5bdf144b90d2c8412c47ccfc83b36
3
+ size 6549796