rajkhanke's picture
Upload 3 files
5a32907 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Farm Intrusion & Livestock 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: #2ecc71; --primary-dark: #27ae60; --secondary-color: #1e8449;
--accent-color: #3498db; --background-color: #f5f5f5; --card-color: #ffffff;
--text-color: #333333; --text-light: #7f8c8d; --border-color: #e0e0e0;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1); --border-radius: 8px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
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; }
.header { background-color: var(--primary-color); color: white; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; box-shadow: var(--shadow); }
.header h1 { font-size: 1.5rem; margin: 0; font-weight: 600; }
.container { display: flex; flex: 1; }
.sidebar { width: 350px; background-color: var(--card-color); border-right: 1px solid var(--border-color); padding: 1.5rem; overflow-y: auto; box-shadow: 2px 0 5px rgba(0,0,0,0.05); }
.main-content { flex: 1; padding: 2rem; overflow-y: auto; display: flex; flex-direction: column; gap: 1.5rem; }
.section-title { color: var(--primary-dark); font-size: 1.2rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid var(--primary-color); }
.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); }
.video-feed { width: 100%; border-radius: var(--border-radius); display: block; }
.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; }
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
.detection-list { list-style: none; margin-top: 0.5rem; }
.detection-item { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem; border-bottom: 1px solid var(--border-color); }
.detection-item:last-child { border-bottom: none; }
.detection-count { background-color: var(--primary-color); color: white; padding: 2px 10px; border-radius: 12px; font-size: 0.9rem; font-weight: bold; }
.alert-info { background-color: rgba(46, 204, 113, 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; }
#graph { min-height: 250px; }
@media (max-width: 992px) { .container { flex-direction: column; } .sidebar { width: 100%; border-right: none; border-bottom: 1px solid var(--border-color); } }
@media (max-width: 576px) { .header { padding: 1rem; } .main-content, .sidebar { padding: 1rem; } }
</style>
</head>
<body>
<header class="header">
<h1><i class="fas fa-video"></i>Farm Intrusion & Livestock Detection</h1>
<div id="current-time"></div>
</header>
<div class="container">
<div class="sidebar">
<h2 class="section-title">Detection Summary</h2>
<div class="card">
<h3 class="section-title">Location</h3>
<div id="location-info">Requesting location access...</div>
</div>
<div class="card">
<h3 class="section-title">Detected Objects</h3>
<ul id="class-list" class="detection-list">
<li class="detection-item">Awaiting data...</li>
</ul>
</div>
<div class="card">
<h3 class="section-title">Detection Graph</h3>
<div id="bar-graph" style="min-height:180px;"></div>
<div id="line-graph" style="min-height:180px;margin-top:20px;"></div>
<div id="pie-graph" style="min-height:180px;margin-top:20px;"></div>
</div>
</div>
<div class="main-content">
<div class="card">
<h2 class="section-title">Live Camera Feed</h2>
<div class="video-container">
<img class="video-feed" src="{{ url_for('video_feed') }}" alt="Live Video Feed">
<div class="status"><span class="status-dot"></span>Live</div>
</div>
<div class="alert-info">
<i class="fas fa-bell"></i>
<p>If a person is detected, an automated call will be initiated and a Telegram alert will be sent.</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// --- TIME UPDATE ---
const timeElement = document.getElementById('current-time');
function updateTime() {
timeElement.textContent = new Date().toLocaleTimeString();
}
setInterval(updateTime, 1000);
updateTime();
// --- LOCATION HANDLING ---
const locationInfo = document.getElementById('location-info');
function sendLocationToServer(lat, lon) {
fetch('/set_location', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ latitude: lat, longitude: lon })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success' || data.location) {
locationInfo.innerHTML = `<b>Address:</b> ${data.location}<br><b>Latitude:</b> ${data.latitude}<br><b>Longitude:</b> ${data.longitude}`;
} else {
locationInfo.textContent = `Lat: ${lat}, Lon: ${lon}`;
}
})
.catch(() => {
locationInfo.textContent = 'Could not send location to server.';
});
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
sendLocationToServer(position.coords.latitude, position.coords.longitude);
},
() => {
locationInfo.textContent = 'Location access denied.';
}
);
} else {
locationInfo.textContent = 'Geolocation is not supported by this browser.';
}
// --- DATA FETCHING AND UI UPDATES ---
const classList = document.getElementById('class-list');
async function fetchAndUpdateDashboard() {
try {
const response = await fetch('/object_stats');
const data = await response.json();
updateClassList(data.object_counts);
updateGraph(data.object_counts);
// Update location info with full details if available
if (data.location && data.latitude && data.longitude) {
locationInfo.innerHTML = `<b>Address:</b> ${data.location}<br><b>Latitude:</b> ${data.latitude}<br><b>Longitude:</b> ${data.longitude}`;
}
} catch (error) {
classList.innerHTML = '<li class="detection-item">Error loading data</li>';
}
}
function updateClassList(counts) {
classList.innerHTML = '';
const objects = Object.keys(counts);
if (objects.length === 0) {
classList.innerHTML = '<li class="detection-item">No objects detected</li>';
return;
}
objects.forEach(className => {
const li = document.createElement('li');
li.className = 'detection-item';
li.innerHTML = `<span>${className.charAt(0).toUpperCase() + className.slice(1)}</span><span class="detection-count">${counts[className]}</span>`;
classList.appendChild(li);
});
}
// --- GRAPH STATE ---
let timeSeries = [];
let timeLabels = [];
let breakdowns = [];
function updateGraph(counts) {
const classNames = Object.keys(counts);
const objectCounts = Object.values(counts);
const now = new Date().toLocaleTimeString();
// Save for time-series (limit to 4 bins)
timeLabels.push(now);
timeSeries.push(objectCounts.reduce((a, b) => a + b, 0));
breakdowns.push(classNames.map((c, i) => `${counts[c]} ${c}`).join(", "));
if (timeLabels.length > 4) {
timeLabels.shift();
timeSeries.shift();
breakdowns.shift();
}
// Bar chart (top)
const barData = [{
x: classNames.map(name => name.charAt(0).toUpperCase() + name.slice(1)),
y: objectCounts,
type: 'bar',
marker: { color: 'rgba(46, 204, 113, 0.8)' }
}];
const barLayout = {
margin: { t: 20, r: 20, l: 40, b: 40 },
xaxis: { title: 'Detected Objects' },
yaxis: { title: 'Count', dtick: 1, tickformat: 'd' },
font: { family: 'Segoe UI, sans-serif' },
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent',
};
Plotly.newPlot('bar-graph', barData, barLayout, { responsive: true, displayModeBar: false });
// Line chart (middle)
const lineData = [{
x: [...timeLabels],
y: [...timeSeries],
type: 'scatter',
mode: 'lines+markers',
name: 'Total Objects',
hovertemplate: '%{y} objects<br>%{text}',
text: breakdowns,
line: { color: 'rgba(39, 174, 96, 1)', width: 3 },
marker: { color: 'rgba(39, 174, 96, 1)', size: 8 }
}];
const lineLayout = {
margin: { t: 20, r: 20, l: 40, b: 40 },
xaxis: { title: 'Time', tickmode: 'array', tickvals: [...timeLabels], ticktext: [...timeLabels], automargin: true },
yaxis: { title: 'Total Object Count', dtick: 1, tickformat: 'd' },
font: { family: 'Segoe UI, sans-serif' },
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent',
hovermode: 'closest',
};
Plotly.newPlot('line-graph', lineData, lineLayout, { responsive: true, displayModeBar: false });
// Pie chart (bottom)
const pieData = [{
labels: classNames.map(name => name.charAt(0).toUpperCase() + name.slice(1)),
values: objectCounts,
type: 'pie',
textinfo: 'label+percent',
insidetextorientation: 'radial',
marker: { colors: ['#2ecc71', '#27ae60', '#3498db', '#1e8449', '#e67e22', '#e74c3c'] }
}];
const pieLayout = {
margin: { t: 20, r: 20, l: 40, b: 40 },
font: { family: 'Segoe UI, sans-serif' },
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent',
};
Plotly.newPlot('pie-graph', pieData, pieLayout, { responsive: true, displayModeBar: false });
}
// Initial call and set interval to fetch every 1 second
fetchAndUpdateDashboard();
setInterval(fetchAndUpdateDashboard, 1000);
});
</script>
</body>
</html>