Add 'api/' from commit '14ac229098e65aa643f84e8e17e0c5f1aaf8d639'
git-subtree-dir: api git-subtree-mainline:4743f19aefgit-subtree-split:14ac229098
This commit is contained in:
336
api/camera_preview.html
Normal file
336
api/camera_preview.html
Normal file
@@ -0,0 +1,336 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>USDA Vision Camera Live Preview</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.camera-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.camera-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.camera-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.camera-stream {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #000;
|
||||
min-height: 200px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.camera-controls {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #1e7e34;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
margin-top: 30px;
|
||||
padding: 15px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.system-info h3 {
|
||||
margin-top: 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.api-info {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎥 USDA Vision Camera Live Preview</h1>
|
||||
|
||||
<div class="camera-grid" id="cameraGrid">
|
||||
<!-- Camera cards will be dynamically generated -->
|
||||
</div>
|
||||
|
||||
<div class="system-info">
|
||||
<h3>📡 System Information</h3>
|
||||
<div id="systemStatus">Loading system status...</div>
|
||||
|
||||
<h3>🔗 API Endpoints</h3>
|
||||
<div class="api-info">
|
||||
<p><strong>Live Stream:</strong> GET /cameras/{camera_name}/stream</p>
|
||||
<p><strong>Start Stream:</strong> POST /cameras/{camera_name}/start-stream</p>
|
||||
<p><strong>Stop Stream:</strong> POST /cameras/{camera_name}/stop-stream</p>
|
||||
<p><strong>Camera Status:</strong> GET /cameras</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://vision:8000';
|
||||
let cameras = {};
|
||||
|
||||
// Initialize the page
|
||||
async function init() {
|
||||
await loadCameras();
|
||||
await loadSystemStatus();
|
||||
|
||||
// Refresh status every 5 seconds
|
||||
setInterval(loadSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// Load camera information
|
||||
async function loadCameras() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/cameras`);
|
||||
const data = await response.json();
|
||||
cameras = data;
|
||||
renderCameras();
|
||||
} catch (error) {
|
||||
console.error('Error loading cameras:', error);
|
||||
showError('Failed to load camera information');
|
||||
}
|
||||
}
|
||||
|
||||
// Load system status
|
||||
async function loadSystemStatus() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/system/status`);
|
||||
const data = await response.json();
|
||||
|
||||
const statusDiv = document.getElementById('systemStatus');
|
||||
statusDiv.innerHTML = `
|
||||
<p><strong>System:</strong> ${data.status}</p>
|
||||
<p><strong>Uptime:</strong> ${data.uptime}</p>
|
||||
<p><strong>API Server:</strong> ${data.api_server_running ? '✅ Running' : '❌ Stopped'}</p>
|
||||
<p><strong>Camera Manager:</strong> ${data.camera_manager_running ? '✅ Running' : '❌ Stopped'}</p>
|
||||
<p><strong>MQTT Client:</strong> ${data.mqtt_client_connected ? '✅ Connected' : '❌ Disconnected'}</p>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error loading system status:', error);
|
||||
document.getElementById('systemStatus').innerHTML = '<p style="color: red;">Failed to load system status</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Render camera cards
|
||||
function renderCameras() {
|
||||
const grid = document.getElementById('cameraGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
for (const [cameraName, cameraInfo] of Object.entries(cameras)) {
|
||||
const card = createCameraCard(cameraName, cameraInfo);
|
||||
grid.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a camera card
|
||||
function createCameraCard(cameraName, cameraInfo) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'camera-card';
|
||||
card.innerHTML = `
|
||||
<div class="camera-title">${cameraName}</div>
|
||||
<img class="camera-stream" id="stream-${cameraName}"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPk5vIFN0cmVhbTwvdGV4dD48L3N2Zz4="
|
||||
alt="Camera Stream">
|
||||
<div class="camera-controls">
|
||||
<button class="btn btn-success" onclick="startStream('${cameraName}')">Start Stream</button>
|
||||
<button class="btn btn-danger" onclick="stopStream('${cameraName}')">Stop Stream</button>
|
||||
<button class="btn btn-secondary" onclick="refreshStream('${cameraName}')">Refresh</button>
|
||||
</div>
|
||||
<div class="status status-info" id="status-${cameraName}">
|
||||
Status: ${cameraInfo.status} | Recording: ${cameraInfo.is_recording ? 'Yes' : 'No'}
|
||||
</div>
|
||||
`;
|
||||
return card;
|
||||
}
|
||||
|
||||
// Start streaming for a camera
|
||||
async function startStream(cameraName) {
|
||||
try {
|
||||
updateStatus(cameraName, 'Starting stream...', 'info');
|
||||
|
||||
// Start the stream
|
||||
const response = await fetch(`${API_BASE}/cameras/${cameraName}/start-stream`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Set the stream source
|
||||
const streamImg = document.getElementById(`stream-${cameraName}`);
|
||||
streamImg.src = `${API_BASE}/cameras/${cameraName}/stream?t=${Date.now()}`;
|
||||
|
||||
updateStatus(cameraName, 'Stream started successfully', 'success');
|
||||
} else {
|
||||
const error = await response.text();
|
||||
updateStatus(cameraName, `Failed to start stream: ${error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting stream:', error);
|
||||
updateStatus(cameraName, `Error starting stream: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Stop streaming for a camera
|
||||
async function stopStream(cameraName) {
|
||||
try {
|
||||
updateStatus(cameraName, 'Stopping stream...', 'info');
|
||||
|
||||
const response = await fetch(`${API_BASE}/cameras/${cameraName}/stop-stream`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Clear the stream source
|
||||
const streamImg = document.getElementById(`stream-${cameraName}`);
|
||||
streamImg.src = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPk5vIFN0cmVhbTwvdGV4dD48L3N2Zz4=";
|
||||
|
||||
updateStatus(cameraName, 'Stream stopped successfully', 'success');
|
||||
} else {
|
||||
const error = await response.text();
|
||||
updateStatus(cameraName, `Failed to stop stream: ${error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error stopping stream:', error);
|
||||
updateStatus(cameraName, `Error stopping stream: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh stream for a camera
|
||||
function refreshStream(cameraName) {
|
||||
const streamImg = document.getElementById(`stream-${cameraName}`);
|
||||
if (streamImg.src.includes('/stream')) {
|
||||
streamImg.src = `${API_BASE}/cameras/${cameraName}/stream?t=${Date.now()}`;
|
||||
updateStatus(cameraName, 'Stream refreshed', 'info');
|
||||
} else {
|
||||
updateStatus(cameraName, 'No active stream to refresh', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Update status message
|
||||
function updateStatus(cameraName, message, type) {
|
||||
const statusDiv = document.getElementById(`status-${cameraName}`);
|
||||
statusDiv.className = `status status-${type}`;
|
||||
statusDiv.textContent = message;
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
alert(`Error: ${message}`);
|
||||
}
|
||||
|
||||
// Initialize when page loads
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user