Files
mucapy/templates/index.html
2025-05-26 20:29:02 +02:00

321 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Camera Viewer</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--primary-color: #000080;
--bg-color: #c0c0c0;
--window-bg: #ffffff;
--border-light: #ffffff;
--border-dark: #808080;
--border-darker: #404040;
--text-color: #000000;
--button-face: #c0c0c0;
--title-bar: #000080;
--title-text: #ffffff;
--button-highlight: #ffffff;
--button-shadow: #808080;
--button-shadow-dark: #404040;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'IBM Plex Mono', monospace;
}
body {
background-color: var(--bg-color);
padding: 20px;
min-height: 100vh;
}
.window {
background-color: var(--window-bg);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--border-light);
border-left: 2px solid var(--border-light);
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
margin: 0 auto;
max-width: 1600px;
}
.title-bar {
background-color: var(--title-bar);
color: var(--title-text);
padding: 4px 8px;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
}
.title-bar-controls {
display: flex;
gap: 4px;
}
.title-bar-button {
width: 20px;
height: 20px;
background-color: var(--button-face);
border: 1px solid var(--border-darker);
border-top: 1px solid var(--button-highlight);
border-left: 1px solid var(--button-highlight);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.content {
padding: 16px;
}
.camera-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.camera-container {
position: relative;
background-color: var(--bg-color);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--border-light);
border-left: 2px solid var(--border-light);
padding: 8px;
min-height: 300px;
display: flex;
flex-direction: column;
}
.camera-title {
background-color: var(--title-bar);
color: var(--title-text);
padding: 4px 8px;
margin-bottom: 8px;
font-size: 14px;
}
.camera-feed {
flex: 1;
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.camera-feed img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.status-bar {
background-color: var(--button-face);
border-top: 2px solid var(--border-darker);
padding: 4px 8px;
display: flex;
justify-content: space-between;
font-size: 12px;
margin-top: 8px;
}
.control-panel {
background-color: var(--button-face);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--border-light);
border-left: 2px solid var(--border-light);
padding: 16px;
margin-bottom: 16px;
}
.button {
background-color: var(--button-face);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--button-highlight);
border-left: 2px solid var(--button-highlight);
padding: 6px 12px;
cursor: pointer;
font-size: 14px;
min-width: 100px;
text-align: center;
}
.button:active {
border: 2px solid var(--border-darker);
border-bottom: 2px solid var(--button-highlight);
border-right: 2px solid var(--button-highlight);
}
.button.primary {
background-color: var(--primary-color);
color: var(--title-text);
}
.status-message {
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
margin-top: 8px;
}
@media (max-width: 768px) {
.camera-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="window">
<div class="title-bar">
<span>Multi-Camera YOLO Detection</span>
<div class="title-bar-controls">
<div class="title-bar-button">_</div>
<div class="title-bar-button"></div>
<div class="title-bar-button">×</div>
</div>
</div>
<div class="content">
<div class="control-panel">
<button class="button primary" onclick="startSystem()">Start System</button>
<div class="status-message" id="modelStatus">Status: Initializing...</div>
</div>
<div class="camera-grid" id="cameraGrid">
<!-- Camera feeds will be added here dynamically -->
</div>
</div>
<div class="status-bar">
<span id="systemStatus">Ready</span>
<span id="modelName"></span>
</div>
</div>
<script>
let activeCameras = new Set();
let isSystemRunning = false;
async function startSystem() {
if (isSystemRunning) {
return;
}
const startButton = document.querySelector('.button.primary');
startButton.textContent = 'Starting...';
startButton.disabled = true;
try {
// Auto-scan for cameras
const cameras = await scanCameras();
// Initialize camera grid
initializeCameraGrid(cameras);
// Start camera streams
for (const camera of cameras) {
startCameraStream(camera);
}
isSystemRunning = true;
updateStatus('System running');
startButton.textContent = 'System Running';
} catch (error) {
console.error('Error starting system:', error);
updateStatus('Error starting system');
startButton.textContent = 'Start System';
startButton.disabled = false;
}
}
async function scanCameras() {
const response = await fetch('/scan_cameras');
const data = await response.json();
return data.cameras;
}
function initializeCameraGrid(cameras) {
const grid = document.getElementById('cameraGrid');
grid.innerHTML = '';
cameras.forEach((camera, index) => {
const container = document.createElement('div');
container.className = 'camera-container';
container.innerHTML = `
<div class="camera-title">Camera ${index + 1}</div>
<div class="camera-feed">
<img id="camera${camera.id}" src="" alt="Camera ${index + 1}"
style="width: ${camera.width}px; height: ${camera.height}px;">
</div>
<div class="status-bar">
<span>Resolution: ${camera.width}x${camera.height}</span>
<span id="status${camera.id}">Connecting...</span>
</div>
`;
grid.appendChild(container);
});
}
function startCameraStream(camera) {
const img = document.getElementById(`camera${camera.id}`);
const statusElement = document.getElementById(`status${camera.id}`);
function updateStream() {
if (!isSystemRunning) return;
img.src = `/video_feed/${camera.id}?t=${Date.now()}`;
statusElement.textContent = 'Connected';
activeCameras.add(camera.id);
img.onload = () => {
if (isSystemRunning) {
requestAnimationFrame(updateStream);
}
};
img.onerror = () => {
statusElement.textContent = 'Error';
activeCameras.delete(camera.id);
setTimeout(() => {
if (isSystemRunning) {
updateStream();
}
}, 1000);
};
}
updateStream();
}
function updateStatus(message) {
document.getElementById('systemStatus').textContent = message;
}
// Check for model on load
fetch('/check_model')
.then(response => response.json())
.then(data => {
const modelStatus = document.getElementById('modelStatus');
const modelName = document.getElementById('modelName');
if (data.model_loaded) {
modelStatus.textContent = 'Status: Model loaded';
modelName.textContent = `Model: ${data.model_name || 'Unknown'}`;
} else {
modelStatus.textContent = 'Status: No model loaded';
}
});
</script>
</body>
</html>
</html>