Files
2026-06-12 23:20:00 +02:00

256 lines
8.2 KiB
JavaScript
Executable File
Raw Permalink 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.
// ==== FRONTEND app.js ====
// Service-Liste
const services = [
{ id: "abc", name: "Control GamePad", url: "https://tccontrol.server.schooltech.ch/" },
{ id: "xyz", name: "Guacamole", url: "https://rp5guac.server.schooltech.ch/" },
{ id: "sim", name: "Simulation", url: "https://tcSimulation.server.schooltech.ch/" },
{ id: "video", name: "Video", url: "https://robotVideo.server.schooltech.ch/" },
{ id: "homing", name: "Homing", url:"https://robotHoming.server.schooltech.ch/"},
{ id: "base", name:"RobotBase", url:"https://robotBase.server.schooltech.ch/"},
{ id: "ellbow", name:"RobotEllbow", url:"https://robotEllbow.server.schooltech.ch/"},
{ id: "hand", name:"RobotHand", url:"https://robotHand.server.schooltech.ch/"},
{ id: "driver", name:"RobotDriver", url:"https://robotdriver.server.schooltech.ch/"},
{ id: "code", name:"VSCode", url:"https://robotVSCode.server.schooltech.ch/"}
];
// DOM-Elemente
const iframe = document.getElementById("service-frame");
const loginModal = document.getElementById("login-modal");
const loginBtn = document.getElementById("login-btn");
const loginSubmit = document.getElementById("login-submit");
const loginMsg = document.getElementById("login-msg");
const nav = document.getElementById("services");
const estopBtn = document.getElementById("emergency-stop");
const usernameInput = document.getElementById("username");
const passwordInput = document.getElementById("password");
let loggedIn = false;
let armedPollInterval = null;
// ===========================
// Login anzeigen
// ===========================
function switchToLogin() {
loginBtn.textContent = "Login";
loginBtn.onclick = () => {
loginModal.style.display = "block";
};
}
// ===========================
// Logout anzeigen
// ===========================
function switchToLogout() {
loginBtn.textContent = "Logout";
loginBtn.onclick = async () => {
try {
await fetch("/api/logout", { method: "POST" });
} catch (e) {
console.warn("Logout request failed:", e);
}
performLocalLogout();
};
}
// ===========================
// Lokales Logout
// ===========================
function performLocalLogout() {
loggedIn = false;
stopArmedPolling();
hideEstopInstant();
iframe.src = "";
iframe.style.display = "none";
nav.innerHTML = "";
loginModal.style.display = "block";
switchToLogin();
}
// ===========================
// Armed-Status prüfen
// ===========================
const POWER_STATUS_URL = '/api/power-status';
const ESTOP_URL = '/api/emergency-stop';
let estopFadeTimer = null;
const ESTOP_FADE_MS = 10000; // 10s Ausblende-Dauer
// Einblenden: zügig (Sicherheit Button soll prompt da sein)
function showEstop() {
if (estopFadeTimer) { clearTimeout(estopFadeTimer); estopFadeTimer = null; }
estopBtn.style.transition = "opacity 0.3s ease, transform 0.1s ease";
estopBtn.style.pointerEvents = "auto";
estopBtn.style.display = "block";
void estopBtn.offsetWidth; // Reflow erzwingen -> Transition startet sauber
estopBtn.style.opacity = "1";
}
// Ausblenden: langsam über 10s, danach display:none
function fadeOutEstop() {
// schon weg oder ein Fade läuft bereits? -> nichts tun (kein Neustart des Timers)
if (estopBtn.style.display === "none" || estopFadeTimer) return;
estopBtn.style.transition = "opacity " + (ESTOP_FADE_MS / 1000) + "s ease, transform 0.1s ease";
estopBtn.style.pointerEvents = "none"; // während Ausblenden nicht mehr klickbar
estopBtn.style.opacity = "0";
estopFadeTimer = setTimeout(() => {
estopBtn.style.display = "none";
estopFadeTimer = null;
}, ESTOP_FADE_MS);
}
// Sofort verstecken ohne Animation (z.B. Logout)
function hideEstopInstant() {
if (estopFadeTimer) { clearTimeout(estopFadeTimer); estopFadeTimer = null; }
estopBtn.style.transition = "none";
estopBtn.style.pointerEvents = "none";
estopBtn.style.opacity = "0";
estopBtn.style.display = "none";
}
async function updateArmedStatus() {
console.log('[armed-check] GET', POWER_STATUS_URL);
try {
const r = await fetch(POWER_STATUS_URL);
if (r.ok) {
const data = await r.json();
console.log('[armed-check] response:', data);
if (data.armed) showEstop(); else fadeOutEstop();
} else {
console.warn('[armed-check] HTTP', r.status, ' Button wird ausgeblendet');
fadeOutEstop();
}
} catch (e) {
console.error('[armed-check] Fehler bei', POWER_STATUS_URL, '', e.message);
fadeOutEstop();
}
}
function startArmedPolling() {
updateArmedStatus();
armedPollInterval = setInterval(updateArmedStatus, 5000);
}
function stopArmedPolling() {
if (armedPollInterval) {
clearInterval(armedPollInterval);
armedPollInterval = null;
}
}
// ===========================
// Emergency Stop
// ===========================
estopBtn.addEventListener("click", async () => {
console.log('[estop] POST', ESTOP_URL);
try {
const r = await fetch(ESTOP_URL, { method: 'POST' });
console.log('[estop] response HTTP', r.status);
} catch (e) {
console.error('[estop] Fehler bei', ESTOP_URL, '', e.message);
}
});
// ===========================
// Login-Logik
// ===========================
async function doLogin() {
const user = usernameInput.value;
const pass = passwordInput.value;
try {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user, pass })
});
if (res.ok) {
loggedIn = true;
loginModal.style.display = "none";
loginMsg.textContent = "";
setupServiceButtons();
switchToLogout();
startArmedPolling();
} else {
loginMsg.textContent = "Login fehlgeschlagen";
}
} catch (e) {
loginMsg.textContent = "Fehler: " + e.message;
}
}
loginSubmit.onclick = doLogin;
// Enter-Taste Login
[usernameInput, passwordInput].forEach(input => {
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
doLogin();
}
});
});
// ===========================
// Buttons erzeugen
// ===========================
function setupServiceButtons() {
nav.innerHTML = "";
services.forEach(svc => {
console.log("Service " + svc.name + " wird als Button angefuegt");
const btn = document.createElement("button");
btn.textContent = svc.name;
btn.onclick = async () => {
try {
await fetch('/api/event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: 'open',
service: svc.id,
url: svc.url
})
});
} catch(e) {
console.error('Event log failed', e);
}
document.querySelectorAll("nav button").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
openService(svc);
};
nav.appendChild(btn);
});
}
// ===========================
// Service öffnen
// ===========================
function openService(svc) {
iframe.src = svc.url;
iframe.style.display = "block";
window.scrollTo(0,0);
}
// ===========================
// Session Status beim Laden prüfen
// ===========================
(async function checkStatus() {
try {
const r = await fetch('/api/status');
if (r.ok) {
loggedIn = true;
setupServiceButtons();
switchToLogout();
startArmedPolling();
} else {
switchToLogin();
}
} catch (e) {
switchToLogin();
}
})();