INTRUSION1 / templates /index.html
sikeaditya's picture
Upload 6 files
e93d13e verified
raw
history blame
37.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Security Monitoring System</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #2563eb;
--primary-dark: #1d4ed8;
--primary-light: #3b82f6;
--secondary-color: #10b981;
--danger-color: #ef4444;
--warning-color: #f59e0b;
--neutral-dark: #171717;
--neutral: #262626;
--neutral-light: #404040;
--background-color: #f9fafb;
--card-color: #ffffff;
--text-color: #111827;
--text-secondary: #6b7280;
--text-light: #9ca3af;
--border-color: #e5e7eb;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
--border-radius: 0.5rem;
}
/* Dark mode */
.dark-mode {
--primary-color: #3b82f6;
--primary-dark: #2563eb;
--primary-light: #60a5fa;
--secondary-color: #10b981;
--background-color: #0f172a;
--card-color: #1e293b;
--text-color: #f9fafb;
--text-secondary: #cbd5e1;
--text-light: #94a3b8;
--border-color: #334155;
--neutral-dark: #f9fafb;
--neutral: #e5e7eb;
--neutral-light: #d1d5db;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
transition: background-color 0.3s, color 0.3s;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
display: flex;
flex-direction: column;
min-height: 100vh;
position: relative;
}
.header {
background-color: var(--card-color);
color: var(--text-color);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 100;
}
.header-title {
display: flex;
align-items: center;
gap: 0.75rem;
}
.header-title i {
color: var(--primary-color);
}
.header h1 {
font-size: 1.25rem;
margin: 0;
font-weight: 600;
}
.header-actions {
display: flex;
align-items: center;
gap: 1.5rem;
}
.header-actions div {
display: flex;
align-items: center;
gap: 0.5rem;
}
.header-actions i {
font-size: 1.25rem;
color: var(--primary-color);
}
.container {
display: flex;
flex: 1;
position: relative;
}
.sidebar {
width: 320px;
background-color: var(--card-color);
border-right: 1px solid var(--border-color);
padding: 1.5rem;
overflow-y: auto;
transition: all 0.3s ease;
height: calc(100vh - 64px);
position: sticky;
top: 64px;
}
.sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 0.75rem;
border-bottom: 2px solid var(--primary-color);
}
.main-content {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.section-title {
color: var(--text-color);
font-size: 1.1rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--primary-color);
display: flex;
align-items: center;
justify-content: space-between;
}
.section-title button {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
border-radius: var(--border-radius);
}
.section-title button:hover {
background-color: rgba(59, 130, 246, 0.1);
}
.card {
background-color: var(--card-color);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
padding: 1.5rem;
margin-bottom: 1.5rem;
border-top: 4px solid var(--primary-color);
}
.video-container {
position: relative;
overflow: hidden;
border-radius: var(--border-radius);
background-color: #000;
box-shadow: var(--shadow);
aspect-ratio: 16/9;
}
.video-feed {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: var(--border-radius);
display: block;
border: none;
}
.status {
position: absolute;
top: 15px;
right: 15px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 0.5rem 0.75rem;
border-radius: 20px;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
height: 10px;
width: 10px;
background-color: var(--primary-color);
border-radius: 50%;
display: inline-block;
animation: pulse 1.5s infinite;
}
.feed-controls {
display: flex;
justify-content: space-between;
margin-top: 1rem;
}
.camera-controls {
display: flex;
gap: 1rem;
}
.control-btn {
background-color: var(--card-color);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
padding: 0.5rem 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.9rem;
}
.control-btn:hover {
background-color: var(--primary-color);
color: white;
}
.alert-btn {
background-color: var(--danger-color);
color: white;
border: none;
}
.alert-btn:hover {
background-color: #dc2626;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.detection-list {
list-style: none;
margin-top: 0.5rem;
max-height: 300px;
overflow-y: auto;
}
.detection-item {
display: flex;
justify-content: space-between;
padding: 0.75rem;
border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s ease;
align-items: center;
}
.detection-item:hover {
background-color: rgba(59, 130, 246, 0.1);
}
.detection-item:last-child {
border-bottom: none;
}
.detection-label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.detection-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.detection-count {
background-color: var(--primary-color);
color: white;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
min-width: 2rem;
text-align: center;
}
.alert-info {
background-color: rgba(59, 130, 246, 0.1);
border-left: 4px solid var(--primary-color);
padding: 1rem;
margin-top: 1rem;
border-radius: 4px;
display: flex;
align-items: center;
gap: 10px;
}
.alert-info i {
color: var(--primary-color);
font-size: 1.2rem;
}
.alert-history {
max-height: 300px;
overflow-y: auto;
}
.alert-card {
padding: 1rem;
border-radius: var(--border-radius);
margin-bottom: 1rem;
border-left: 4px solid var(--danger-color);
background-color: rgba(239, 68, 68, 0.1);
}
.alert-card:last-child {
margin-bottom: 0;
}
.alert-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--danger-color);
}
.alert-details {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.alert-tag {
background-color: var(--danger-color);
color: white;
border-radius: 12px;
padding: 0.25rem 0.75rem;
font-size: 0.8rem;
}
#graph-container {
height: 300px;
margin-top: 1rem;
}
/* Stats cards */
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.stat-card {
background-color: var(--card-color);
border-radius: var(--border-radius);
padding: 1.25rem;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.stat-title {
color: var(--text-secondary);
font-size: 0.9rem;
}
.stat-value {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-color);
}
.stat-comparison {
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 0.25rem;
}
.stat-up {
color: var(--secondary-color);
}
.stat-down {
color: var(--danger-color);
}
/* Switch */
.switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--border-color);
-webkit-transition: .4s;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--primary-color);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--primary-color);
}
input:checked + .slider:before {
-webkit-transform: translateX(24px);
-ms-transform: translateX(24px);
transform: translateX(24px);
}
/* Responsive */
@media (max-width: 1024px) {
.container {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
position: relative;
top: 0;
border-right: none;
border-bottom: 1px solid var(--border-color);
}
.main-content {
padding: 1rem;
}
}
@media (max-width: 768px) {
.header {
padding: 1rem;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.stats-container {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 576px) {
.stats-container {
grid-template-columns: 1fr;
}
.camera-controls {
flex-wrap: wrap;
}
.feed-controls {
flex-direction: column;
gap: 1rem;
}
}
</style>
</head>
<body>
<header class="header">
<div class="header-title">
<i class="fas fa-shield-alt"></i>
<h1>Advanced Security Monitoring System</h1>
</div>
<div class="header-actions">
<div>
<i class="fas fa-clock"></i>
<span id="current-time"></span>
</div>
<div>
<i class="fas fa-moon"></i>
<label class="switch">
<input type="checkbox" id="dark-mode-toggle">
<span class="slider"></span>
</label>
</div>
</div>
</header>
<div class="container">
<div class="sidebar">
<div class="stats-container">
<div class="stat-card" style="border-left: 4px solid var(--primary-color);">
<div class="stat-title">Total Detections</div>
<div class="stat-value" id="total-detections">0</div>
<div class="stat-comparison stat-up">
<i class="fas fa-arrow-up"></i>
<span id="detection-rate">Calculating...</span>
</div>
</div>
<div class="stat-card" style="border-left: 4px solid var(--danger-color);">
<div class="stat-title">Alerts Today</div>
<div class="stat-value" id="alerts-today">0</div>
<div class="stat-comparison">
<span id="last-alert">No alerts yet</span>
</div>
</div>
</div>
<div class="card">
<div class="section-title">
<span>Detected Objects</span>
<button id="reset-counts">
<i class="fas fa-redo-alt"></i>
Reset
</button>
</div>
<ul id="class-list" class="detection-list">
<li class="detection-item">Loading data...</li>
</ul>
</div>
<div class="card">
<div class="section-title">Detection Trend</div>
<div id="graph-container"></div>
</div>
<div class="card">
<div class="section-title">Recent Alerts</div>
<div id="alert-history" class="alert-history">
<div class="alert-card">
<div class="alert-header">
<span>Loading alerts...</span>
</div>
</div>
</div>
</div>
</div>
<div class="main-content">
<div class="card">
<div class="section-title">Live Camera Feed</div>
<div class="video-container">
<img id="video-feed" class="video-feed" src="{{ url_for('video_feed') }}" alt="Live Video Feed">
<div class="status">
<span class="status-dot"></span>
Live Monitoring
</div>
</div>
<div class="feed-controls">
<div class="camera-controls">
<button class="control-btn">
<i class="fas fa-sync-alt"></i>
Refresh
</button>
<button class="control-btn">
<i class="fas fa-camera"></i>
Snapshot
</button>
<button class="control-btn">
<i class="fas fa-expand"></i>
Fullscreen
</button>
</div>
<button class="control-btn alert-btn">
<i class="fas fa-exclamation-triangle"></i>
Test Alert
</button>
</div>
<div class="alert-info">
<i class="fas fa-bell"></i>
<p>Automatic alerts will be sent if security threats are detected. The system will make a phone call and send notifications via Telegram.</p>
</div>
</div>
<div class="dashboard-grid">
<div class="card">
<div class="section-title">Detection Distribution</div>
<div id="pie-chart" style="height: 300px;"></div>
</div>
<div class="card">
<div class="section-title">Alert Configuration</div>
<form id="alert-config">
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem;">Detection Threshold</label>
<input type="range" min="0" max="100" value="50" id="detection-threshold" style="width: 100%;">
<div style="display: flex; justify-content: space-between; margin-top: 0.25rem;">
<span>Low</span>
<span>High</span>
</div>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem;">Alert Interval (seconds)</label>
<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);">
</div>
<div style="margin-bottom: 1rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<input type="checkbox" id="enable-calls" checked>
Enable Phone Calls
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<input type="checkbox" id="enable-telegram" checked>
Enable Telegram
</label>
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" id="enable-sounds" checked>
Enable Sound Alerts
</label>
</div>
<button type="submit" class="control-btn" style="width: 100%; background-color: var(--primary-color); color: white;">
<i class="fas fa-save"></i>
Save Configuration
</button>
</form>
</div>
</div>
</div>
</div>
<!-- Load the Charts.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<script>
// Dark mode toggle
const darkModeToggle = document.getElementById('dark-mode-toggle');
const body = document.body;
// Check for saved dark mode preference
if (localStorage.getItem('darkMode') === 'enabled') {
body.classList.add('dark-mode');
darkModeToggle.checked = true;
}
darkModeToggle.addEventListener('change', () => {
if (darkModeToggle.checked) {
body.classList.add('dark-mode');
localStorage.setItem('darkMode', 'enabled');
} else {
body.classList.remove('dark-mode');
localStorage.setItem('darkMode', null);
}
});
// Current time display
function updateTime() {
const now = new Date();
const timeElement = document.getElementById('current-time');
timeElement.textContent = now.toLocaleTimeString();
}
// Update time every second
setInterval(updateTime, 1000);
updateTime(); // Initial call
// Tracking detection counts and history
let detectionCounts = {};
let alertHistory = [];
let totalDetections = 0;
const detectionData = [];
const chartLabels = [];
// Generate some initial data for the trend chart
for (let i = 0; i < 10; i++) {
const date = new Date();
date.setMinutes(date.getMinutes() - (9 - i));
chartLabels.push(date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}));
detectionData.push(0);
}
// Create trend chart
const trendCtx = document.createElement('canvas');
document.getElementById('graph-container').appendChild(trendCtx);
const trendChart = new Chart(trendCtx, {
type: 'line',
data: {
labels: chartLabels,
datasets: [{
label: 'Detections',
data: detectionData,
backgroundColor: 'rgba(59, 130, 246, 0.2)',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 2,
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
// Create pie chart for detection distribution
const pieCtx = document.createElement('canvas');
document.getElementById('pie-chart').appendChild(pieCtx);
const pieChart = new Chart(pieCtx, {
type: 'doughnut',
data: {
labels: [],
datasets: [{
data: [],
backgroundColor: [
'rgba(59, 130, 246, 0.8)',
'rgba(16, 185, 129, 0.8)',
'rgba(239, 68, 68, 0.8)',
'rgba(245, 158, 11, 0.8)',
'rgba(139, 92, 246, 0.8)',
'rgba(236, 72, 153, 0.8)',
'rgba(20, 184, 166, 0.8)',
'rgba(249, 115, 22, 0.8)',
'rgba(168, 85, 247, 0.8)',
'rgba(217, 70, 239, 0.8)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right'
}
}
}
});
// Function to update the detection list display
function updateDetectionList() {
const list = document.getElementById('class-list');
list.innerHTML = '';
// Sort by count (highest first)
const sortedClasses = Object.keys(detectionCounts).sort((a, b) =>
detectionCounts[b] - detectionCounts[a]
);
if (sortedClasses.length === 0) {
const listItem = document.createElement('li');
listItem.className = 'detection-item';
listItem.textContent = 'No detections yet';
list.appendChild(listItem);
return;
}
sortedClasses.forEach(className => {
const count = detectionCounts[className];
const listItem = document.createElement('li');
listItem.className = 'detection-item';
const label = document.createElement('div');
label.className = 'detection-label';
const icon = document.createElement('div');
icon.className = 'detection-icon';
// Choose appropriate icon based on class name
let iconClass = 'fas fa-box';
if (className.toLowerCase().includes('person')) {
iconClass = 'fas fa-user';
} else if (className.toLowerCase().includes('dog') ||
className.toLowerCase().includes('cat') ||
className.toLowerCase().includes('animal') ||
className.toLowerCase().includes('bird')) {
iconClass = 'fas fa-paw';
} else if (className.toLowerCase().includes('car') ||
className.toLowerCase().includes('truck') ||
className.toLowerCase().includes('vehicle')) {
iconClass = 'fas fa-car';
}
const iconElement = document.createElement('i');
iconElement.className = iconClass;
icon.appendChild(iconElement);
const nameSpan = document.createElement('span');
nameSpan.textContent = className;
label.appendChild(icon);
label.appendChild(nameSpan);
const countElement = document.createElement('div');
countElement.className = 'detection-count';
countElement.textContent = count;
listItem.appendChild(label);
listItem.appendChild(countElement);
list.appendChild(listItem);
});
// Update pie chart
pieChart.data.labels = sortedClasses;
pieChart.data.datasets[0].data = sortedClasses.map(cls => detectionCounts[cls]);
pieChart.update();
}
// Function to update alert history display
function updateAlertHistory() {
const alertContainer = document.getElementById('alert-history');
alertContainer.innerHTML = '';
if (alertHistory.length === 0) {
const alertCard = document.createElement('div');
alertCard.className = 'alert-card';
alertCard.innerHTML = `
<div class="alert-header">
<span>No alerts yet</span>
</div>
<p>Alert history will appear here when security events are detected.</p>
`;
alertContainer.appendChild(alertCard);
return;
}
// Display latest alerts first
alertHistory.slice().reverse().forEach(alert => {
const alertCard = document.createElement('div');
alertCard.className = 'alert-card';
const alertHeader = document.createElement('div');
alertHeader.className = 'alert-header';
const alertTime = document.createElement('span');
alertTime.textContent = alert.time;
alertHeader.appendChild(alertTime);
const alertDetails = document.createElement('div');
alertDetails.className = 'alert-details';
alert.objects.forEach(obj => {
const alertTag = document.createElement('span');
alertTag.className = 'alert-tag';
alertTag.textContent = obj;
alertDetails.appendChild(alertTag);
});
alertCard.appendChild(alertHeader);
alertCard.appendChild(alertDetails);
alertContainer.appendChild(alertCard);
});
// Update alerts today count
document.getElementById('alerts-today').textContent = alertHistory.length;
// Update last alert time if there are alerts
if (alertHistory.length > 0) {
const lastAlert = alertHistory[alertHistory.length - 1];
document.getElementById('last-alert').textContent = 'Last: ' + lastAlert.time.split(' ')[1];
}
}
// Function to fetch detection data
async function fetchDetectionData() {
try {
const response = await fetch('/detection_data');
if (response.ok) {
const data = await response.json();
detectionCounts = data;
totalDetections = Object.values(detectionCounts).reduce((sum, count) => sum + count, 0);
document.getElementById('total-detections').textContent = totalDetections;
// Add new data point for trend chart
const now = new Date();
chartLabels.push(now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}));
chartLabels.shift();
// Get the latest minute's detections
const latestDetections = Object.values(detectionCounts).reduce((sum, count) => sum + count, 0) -
detectionData.reduce((sum, count) => sum + count, 0);
detectionData.push(latestDetections > 0 ? latestDetections : 0);
detectionData.shift();
trendChart.update();
// Calculate detection rate
const detectionRate = document.getElementById('detection-rate');
if (detectionData.slice(-3).some(val => val > 0)) {
detectionRate.textContent = 'Active detections';
detectionRate.parentElement.className = 'stat-comparison stat-up';
detectionRate.previousElementSibling.className = 'fas fa-arrow-up';
} else {
detectionRate.textContent = 'No recent activity';
detectionRate.parentElement.className = 'stat-comparison stat-down';
detectionRate.previousElementSibling.className = 'fas fa-arrow-down';
}
updateDetectionList();
}
} catch (error) {
console.error('Error fetching detection data:', error);
}
}
// Function to fetch alert history
async function fetchAlertHistory() {
try {
const response = await fetch('/alert_history');
if (response.ok) {
alertHistory = await response.json();
updateAlertHistory();
}
} catch (error) {
console.error('Error fetching alert history:', error);
}
}
// Reset detection counts
document.getElementById('reset-counts').addEventListener('click', async () => {
try {
const response = await fetch('/reset_counts');
if (response.ok) {
detectionCounts = {};
totalDetections = 0;
document.getElementById('total-detections').textContent = totalDetections;
updateDetectionList();
// Reset trend chart
detectionData.fill(0);
trendChart.update();
// Reset pie chart
pieChart.data.labels = [];
pieChart.data.datasets[0].data = [];
pieChart.update();
}
} catch (error) {
console.error('Error resetting detection counts:', error);
}
});
// Test alert button
document.querySelector('.alert-btn').addEventListener('click', () => {
const testAlert = {
time: new Date().toLocaleString(),
objects: ['Test Alert'],
counts: { 'Test Alert': 1 }
};
alertHistory.push(testAlert);
if (alertHistory.length > 10) {
alertHistory.shift();
}
updateAlertHistory();
});
// Handle alert configuration form submission
document.getElementById('alert-config').addEventListener('submit', (e) => {
e.preventDefault();
const threshold = document.getElementById('detection-threshold').value;
const interval = document.getElementById('alert-interval').value;
const enableCalls = document.getElementById('enable-calls').checked;
const enableTelegram = document.getElementById('enable-telegram').checked;
const enableSounds = document.getElementById('enable-sounds').checked;
// In a real application, this would send the configuration to the server
const config = {
threshold,
interval,
enableCalls,
enableTelegram,
enableSounds
};
console.log('Alert configuration saved:', config);
// Show a confirmation message
alert('Alert configuration saved successfully!');
});
// Refresh data every 5 seconds
setInterval(fetchDetectionData, 5000);
setInterval(fetchAlertHistory, 10000);
// Initial data fetch
fetchDetectionData();
fetchAlertHistory();
</script>
</body>
</html>