// static/js/app.js – MK Control Frontend
document.addEventListener('DOMContentLoaded', () => {
console.log('MK Control Frontend geladen');
// ── Elemente ───────────────────────────────────────────────────────────────
const connectBtn = document.getElementById('connect-btn');
const connectSection = document.getElementById('connect-section');
const controlSection = document.getElementById('control-section');
const channelsContainer = document.getElementById('channels-container');
const stopAllBtn = document.getElementById('stop-all-btn');
const reconnectSection = document.getElementById('reconnect-section');
const reconnectBtn = document.getElementById('reconnect-btn');
if (!connectBtn) {
console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden');
return;
}
// ── Config aus Template ────────────────────────────────────────────────────
const config = window.mkConfig || {};
console.log("app.js verwendet config:", config);
// ── Connect-Button ────────────────────────────────────────────────────────
connectBtn.addEventListener('click', async () => {
connectBtn.disabled = true;
connectBtn.innerHTML = ' Verbinde...';
try {
console.log("→ Sende /api/connect ...");
const response = await fetch('/api/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
console.log("→ Antwort erhalten:", response.status);
const result = await response.json();
console.log("→ Resultat:", result);
if (result.success) {
console.log("→ Connect erfolgreich – blende Sections um");
connectSection.style.display = 'none';
controlSection.style.display = 'block';
reconnectSection.style.display = 'none';
// Status aktualisieren
updateStatus(true);
console.log("→ Rufe renderChannels() auf");
renderChannels();
console.log("→ renderChannels() abgeschlossen");
// Automatische Prüfung starten
startConnectionCheck();
} else {
console.warn("→ Connect fehlgeschlagen:", result.message);
alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler'));
updateStatus(false);
}
} catch (err) {
console.error("→ Connect-Fehler:", err);
alert('Netzwerk- oder Verbindungsfehler: ' + err.message);
updateStatus(false);
} finally {
connectBtn.disabled = false;
connectBtn.innerHTML = ' Mit Hub verbinden';
}
});
// ── Alle stoppen ──────────────────────────────────────────────────────────
if (stopAllBtn) {
stopAllBtn.addEventListener('click', async () => {
if (!confirm('Wirklich ALLE Kanäle stoppen?')) return;
try {
const res = await fetch('/api/stop_all', { method: 'POST' });
const data = await res.json();
if (data.success) {
// Alle Motor-Slider zurücksetzen
document.querySelectorAll('.motor-slider').forEach(slider => {
slider.value = 0;
const display = slider.parentElement.querySelector('.value-display');
if (display) display.textContent = '0 %';
});
console.log('Alle Kanäle gestoppt');
alert('Alle Kanäle gestoppt');
} else {
alert('Fehler beim Stoppen:\n' + (data.message || 'Unbekannt'));
}
} catch (err) {
console.error('Stop-all Fehler:', err);
alert('Netzwerkfehler beim Stoppen aller Kanäle');
}
});
}
// ── Erneut verbinden ──────────────────────────────────────────────────────
if (reconnectBtn) {
reconnectBtn.addEventListener('click', async () => {
reconnectBtn.disabled = true;
reconnectBtn.innerHTML = ' Verbinde...';
try {
const response = await fetch('/api/reconnect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.success) {
reconnectSection.style.display = 'none';
controlSection.style.display = 'block';
updateStatus(true);
startConnectionCheck();
alert('Verbindung wiederhergestellt!');
} else {
alert('Erneute Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannt'));
}
} catch (err) {
console.error('Reconnect-Fehler:', err);
alert('Netzwerkfehler beim erneuten Verbinden');
} finally {
reconnectBtn.disabled = false;
reconnectBtn.innerHTML = ' Erneut verbinden';
}
});
}
// ── Kanäle dynamisch rendern ──────────────────────────────────────────────
function renderChannels() {
console.log("renderChannels() START");
console.log("→ config.channels existiert?", !!config?.channels);
console.log("→ channels.length:", config?.channels?.length ?? "undefined");
if (!channelsContainer) {
console.error("→ channelsContainer nicht gefunden im DOM!");
return;
}
channelsContainer.innerHTML = '';
console.log("→ Container geleert");
if (!config?.channels || config.channels.length === 0) {
console.warn("→ Keine Kanäle erkannt – zeige leere Meldung");
channelsContainer.innerHTML = '
Keine Kanäle in der Konfiguration definiert.
';
return;
}
console.log("→ Beginne mit Rendern von", config.channels.length, "Kanälen");
config.channels.forEach(channel => {
const col = document.createElement('div');
col.className = 'col-md-6 mb-4';
let controlHTML = '';
if (channel.type === 'motor') {
controlHTML = `
-100 %
0 %
+100 %
`;
} else {
// Licht, Sound, Fogger etc. → Toggle
controlHTML = `
`;
}
col.innerHTML = `
`;
channelsContainer.appendChild(col);
});
// ── Event-Listener Slider (Motoren) ─────────────────────────────────────
document.querySelectorAll('.motor-slider').forEach(slider => {
const display = slider.parentElement.querySelector('.value-display');
slider.addEventListener('input', async () => {
const value = parseInt(slider.value) / 100;
if (display) display.textContent = `${slider.value} %`;
try {
await sendControl(slider.dataset.port, value);
} catch (err) {
console.error('Slider-Steuerfehler:', err);
}
});
});
// ── Event-Listener Einzel-Stop pro Kanal ────────────────────────────────
document.querySelectorAll('.stop-channel-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const port = btn.dataset.port;
try {
await sendControl(port, 0);
// Slider zurücksetzen
const slider = document.querySelector(`input[data-port="${port}"]`);
if (slider) {
slider.value = 0;
const display = slider.parentElement.querySelector('.value-display');
if (display) display.textContent = '0 %';
}
// Feedback
btn.classList.add('btn-danger');
setTimeout(() => btn.classList.remove('btn-danger'), 600);
console.log(`Einzelstop: ${port}`);
} catch (err) {
console.error('Einzelstop-Fehler:', err);
}
});
});
// ── Event-Listener Toggle-Buttons (Licht/Sound/Fogger) ──────────────────
document.querySelectorAll('.toggle-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const current = btn.dataset.state;
const newState = current === 'off' ? 'on' : 'off';
btn.dataset.state = newState;
btn.textContent = newState === 'on' ? 'EIN' : 'AUS';
btn.classList.toggle('btn-success', newState === 'on');
btn.classList.toggle('btn-outline-secondary', newState === 'off');
try {
await sendControl(btn.dataset.port, newState === 'on' ? 1 : 0);
} catch (err) {
console.error('Toggle-Fehler:', err);
}
});
});
}
// ── Hilfsfunktion: Steuerbefehl senden ────────────────────────────────────
async function sendControl(port, value) {
try {
const res = await fetch('/api/control', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ port, value })
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.message || 'Steuerbefehl fehlgeschlagen');
}
} catch (err) {
console.error('sendControl Fehler:', err);
failedChecks++;
if (failedChecks >= MAX_FAILED_CHECKS) {
updateStatus(false, 'Mehrere Steuerbefehle fehlgeschlagen');
}
}
}
// ── Hilfsfunktion: Reconnect-Bereich anzeigen ─────────────────────────────
function showReconnect() {
if (reconnectSection && controlSection) {
controlSection.style.display = 'none';
reconnectSection.style.display = 'block';
}
}
// ── Status-Anzeige ─────────────────────────────────────────────────────────
const statusBadge = document.getElementById('connection-status');
function updateStatus(connected, message = '') {
if (!statusBadge) return;
if (connected) {
statusBadge.className = 'badge bg-success px-3 py-2 fs-6';
statusBadge.innerHTML = ' Verbunden';
} else {
statusBadge.className = 'badge bg-danger px-3 py-2 fs-6';
statusBadge.innerHTML = ' Getrennt' + (message ? ` – ${message}` : '');
showReconnect();
}
}
// ── Soundboard Buttons ─────────────────────────────────────────────────────
document.querySelectorAll('.play-sound-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const soundId = btn.dataset.soundId;
btn.disabled = true;
const originalText = btn.innerHTML;
btn.innerHTML = ' Spielt...';
try {
const res = await fetch('/api/play_sound', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sound_id: soundId })
});
const data = await res.json();
if (data.success) {
btn.classList.remove('btn-outline-primary', 'btn-outline-secondary');
btn.classList.add('btn-success');
btn.innerHTML = ' Gespielt';
setTimeout(() => {
btn.classList.remove('btn-success');
btn.classList.add('btn-outline-primary'); // oder secondary
btn.innerHTML = originalText;
btn.disabled = false;
}, 2000);
} else {
alert('Fehler: ' + (data.message || 'Unbekannt'));
btn.disabled = false;
btn.innerHTML = originalText;
}
} catch (err) {
console.error('Sound-Play-Fehler:', err);
alert('Netzwerkfehler beim Abspielen');
btn.disabled = false;
btn.innerHTML = originalText;
}
});
});
// Initialer Status
updateStatus(false);
// ── Automatische Verbindungsprüfung ────────────────────────────────────────
let connectionCheckInterval = null;
let failedChecks = 0;
const MAX_FAILED_CHECKS = 3; // erst nach 3 Fehlschlägen in Folge rot
function startConnectionCheck() {
if (connectionCheckInterval) clearInterval(connectionCheckInterval);
connectionCheckInterval = setInterval(async () => {
try {
console.log("→ Status-Check ...");
const res = await fetch('/api/status');
const data = await res.json();
if (data.connected) {
failedChecks = 0;
updateStatus(true);
} else {
failedChecks++;
console.warn(`Status-Check fehlgeschlagen (${failedChecks}/${MAX_FAILED_CHECKS}):`, data.message);
if (failedChecks >= MAX_FAILED_CHECKS) {
updateStatus(false, data.message || 'Mehrere Checks fehlgeschlagen');
}
}
} catch (err) {
failedChecks++;
console.warn(`Status-Check Netzwerkfehler (${failedChecks}/${MAX_FAILED_CHECKS}):`, err);
if (failedChecks >= MAX_FAILED_CHECKS) {
updateStatus(false, 'Keine Antwort vom Hub/Server');
}
}
}, 6000); // 6 Sekunden – aggressiver als 8 s
}
// In Connect- und Reconnect-Handler nach erfolgreichem Connect/Reconnect:
startConnectionCheck();
updateStatus(true);
window.addEventListener('beforeunload', () => {
if (connectionCheckInterval) clearInterval(connectionCheckInterval);
});
// Optional: Bei Seitenlade den Check starten (falls schon verbunden)
// startConnectionCheck(); // ← auskommentiert, da nach Connect gestartet
});