Files
ExoMy_Cuno/ExoMy_Software-master/gui/admin.html
T
2026-05-21 20:35:30 +02:00

349 lines
12 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>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>CUNO Admin</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body class="admin-page">
<main>
<div class="admin_shell">
<section class="panel admin_panel">
<div class="panel_header">
<div class="pilot_brand">
<a class="pilot_logo_link" href="./index.html" aria-label="Zurück zur Steuerung">
<img class="pilot_logo" src="./Logo_Cuno.png" alt="CUNO Logo" />
</a>
<div>
<p class="eyebrow">Admin</p>
<h1>CUNO System</h1>
</div>
</div>
<p id="admin_status" class="status_badge">Lade...</p>
</div>
<p class="admin_copy">
Diese Seite bündelt die wichtigsten Systemfunktionen und Statuswerte des Raspberry Pi.
</p>
<div class="admin_content">
<div class="admin_section">
<div class="admin_section_header">
<p class="eyebrow">Aktionen und Status</p>
</div>
<div class="admin_action_row">
<div class="admin_actions">
<button class="button buttonWarm" onclick="runAdminAction('restart-camera')">Kameradienst neu starten</button>
<button class="button buttonDanger" onclick="runAdminAction('restart-container')">ExoMy-Container neu starten</button>
<button class="button buttonWarm" onclick="runAdminAction('reboot')">Raspberry Pi neu starten</button>
<button class="button buttonDanger" onclick="runAdminAction('shutdown')">Raspberry Pi herunterfahren</button>
</div>
<div class="service_compact">
<div class="service_compact_row">
<span>cam</span>
<strong id="service_camera" class="service_state">-</strong>
</div>
<div class="service_compact_row">
<span>api</span>
<strong id="service_admin_api" class="service_state">-</strong>
</div>
<div class="service_compact_row">
<span>exo</span>
<strong id="service_exomy" class="service_state">-</strong>
</div>
</div>
</div>
</div>
<div class="admin_section">
<div class="admin_section_header">
<p class="eyebrow">Wartung</p>
</div>
<div class="admin_quick_link_card">
<div>
<strong>Motorentest pro Rad</strong>
<p>Separate Testseite für Lenkung links, Lenkung rechts, Vorwärts und Rückwärts je ausgewähltem Rad.</p>
</div>
<a class="button buttonX admin_quick_link_button" href="./admin-motor-test.html">Motortest öffnen</a>
</div>
</div>
<div class="admin_section">
<div class="admin_section_header">
<p class="eyebrow">Latenz-Simulation</p>
</div>
<div class="admin_quick_link_card">
<div>
<strong>Signallaufzeit</strong>
<p>Verzögert Steuerbefehle (Controller + Web-GUI) und Kamerabild gleichzeitig.</p>
</div>
<div class="delay_control">
<select id="delay_select" class="delay_select" onchange="setDelay(parseFloat(this.value))">
<option value="0">0 s – kein Delay</option>
<option value="1">1 s</option>
<option value="2">2 s</option>
<option value="3">3 s</option>
<option value="4">4 s</option>
<option value="5">5 s</option>
<option value="6">6 s</option>
<option value="7">7 s</option>
<option value="8">8 s</option>
<option value="9">9 s</option>
<option value="10">10 s</option>
</select>
<span id="delay_status" class="delay_status_text"></span>
</div>
</div>
</div>
<div class="admin_section">
<div class="admin_section_header">
<p class="eyebrow">Systemstatus</p>
</div>
<div class="info_grid">
<div class="info_card">
<span class="info_label">WLAN-Status</span>
<strong id="info_wifi">-</strong>
</div>
<div class="info_card">
<span class="info_label">IP-Adressen</span>
<strong id="info_ips">-</strong>
</div>
<div class="info_card">
<span class="info_label">CPU-Temperatur</span>
<strong id="info_temp">-</strong>
</div>
<div class="info_card">
<span class="info_label">Unterspannung</span>
<strong id="info_undervoltage" class="state_unknown">-</strong>
</div>
<div class="info_card">
<span class="info_label">Uptime</span>
<strong id="info_uptime">-</strong>
</div>
<div class="info_card">
<span class="info_label">Freier Speicherplatz</span>
<strong id="info_disk">-</strong>
</div>
</div>
</div>
</div>
<a class="admin_back" href="./index.html">Zurück zur Steuerung</a>
</section>
</div>
</main>
<div id="auth_overlay" class="auth_overlay">
<div class="auth_box">
<p class="eyebrow" style="margin:0;">Admin-Zugang</p>
<h2 style="margin:0;">CUNO System</h2>
<p style="margin:0; font-size:0.88rem; opacity:.6;">Passwort eingeben um fortzufahren</p>
<input type="password" id="auth_input" class="auth_input" placeholder="••••••••" autocomplete="current-password">
<p id="auth_error" class="auth_error"></p>
<button class="button buttonX" onclick="submitPassword()">Entsperren</button>
</div>
</div>
<script type="text/javascript">
var adminApiBase = window.location.protocol + '//' + window.location.hostname + ':8082';
var AUTH_TOKEN = 'Y3Vubw==';
function submitPassword() {
var input = document.getElementById('auth_input');
var error = document.getElementById('auth_error');
if (btoa(input.value) === AUTH_TOKEN) {
document.getElementById('auth_overlay').remove();
} else {
error.textContent = 'Falsches Passwort';
input.value = '';
input.focus();
}
}
function setAdminStatus(text) {
var status = document.getElementById('admin_status');
if (status) {
status.innerHTML = text;
}
}
function setText(id, value) {
var node = document.getElementById(id);
if (node) {
node.textContent = value || '-';
}
}
function setServiceState(id, value) {
var node = document.getElementById(id);
if (!node) {
return;
}
node.textContent = value || '-';
node.classList.remove('state_active', 'state_inactive', 'state_unknown');
if (value === 'active' || value === 'running') {
node.classList.add('state_active');
return;
}
if (value === 'inactive' || value === 'failed' || value === 'exited') {
node.classList.add('state_inactive');
return;
}
node.classList.add('state_unknown');
}
function setUndervoltageState(id, text, state) {
var node = document.getElementById(id);
if (!node) {
return;
}
node.textContent = text || '-';
node.classList.remove('state_active', 'state_inactive', 'state_unknown');
if (state === 'active') {
node.classList.add('state_inactive');
node.style.animation = 'statusBlink 0.9s step-end infinite';
return;
}
node.style.animation = '';
if (state === 'past') {
node.classList.add('state_unknown');
return;
}
if (state === 'clear') {
node.classList.add('state_active');
return;
}
node.classList.add('state_unknown');
}
function renderStatus(data) {
setAdminStatus(data.status || 'Bereit');
setText('info_ips', data.system && data.system.ips);
setText('info_wifi', data.system && data.system.wifi);
setText('info_temp', data.system && data.system.cpu_temperature);
setUndervoltageState(
'info_undervoltage',
data.system && data.system.undervoltage,
data.system && data.system.undervoltage_state
);
setText('info_uptime', data.system && data.system.uptime);
setText('info_disk', data.system && data.system.disk_free);
setServiceState('service_camera', data.services && data.services.camera);
setServiceState('service_admin_api', data.services && data.services.admin_api);
setServiceState('service_exomy', data.services && data.services.exomy);
}
async function fetchStatus() {
try {
var response = await fetch(adminApiBase + '/api/status');
if (!response.ok) {
throw new Error('status ' + response.status);
}
var data = await response.json();
renderStatus(data);
} catch (error) {
console.error(error);
setAdminStatus('API nicht erreichbar');
}
}
async function runAdminAction(action) {
var labels = {
'restart-camera': 'Kameradienst jetzt wirklich neu starten?',
'restart-container': 'ExoMy-Container jetzt wirklich neu starten?',
'reboot': 'Raspberry Pi jetzt wirklich neu starten?',
'shutdown': 'Raspberry Pi jetzt wirklich herunterfahren?'
};
if (!window.confirm(labels[action] || 'Befehl ausführen?')) {
return;
}
setAdminStatus('Sende Befehl...');
try {
var response = await fetch(adminApiBase + '/api/' + action, {
method: 'POST'
});
if (!response.ok) {
throw new Error('status ' + response.status);
}
var data = await response.json();
setAdminStatus(data.status || 'Befehl gesendet');
window.setTimeout(fetchStatus, 2000);
} catch (error) {
console.error(error);
setAdminStatus('Fehler');
alert('Der Admin-Befehl konnte nicht ausgeführt werden.');
}
}
function updateDelayButtons(seconds) {
var sel = document.getElementById('delay_select');
if (sel) sel.value = String(seconds);
}
async function fetchDelay() {
try {
var response = await fetch(adminApiBase + '/api/delay');
if (!response.ok) return;
var data = await response.json();
updateDelayButtons(data.delay_seconds);
} catch (e) {}
}
async function setDelay(seconds) {
var statusEl = document.getElementById('delay_status');
if (statusEl) statusEl.textContent = 'Setze…';
try {
var response = await fetch(adminApiBase + '/api/delay', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({delay_seconds: seconds})
});
var data = await response.json();
updateDelayButtons(seconds);
if (statusEl) statusEl.textContent = data.status || 'Gesetzt';
window.setTimeout(function() { if (statusEl) statusEl.textContent = ''; }, 2000);
} catch (e) {
if (statusEl) statusEl.textContent = 'Fehler';
}
}
window.addEventListener('load', function () {
var input = document.getElementById('auth_input');
input.focus();
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') submitPassword();
});
fetchStatus();
fetchDelay();
window.setInterval(fetchStatus, 10000);
window.setInterval(fetchDelay, 5000);
});
</script>
</body>
</html>