291 lines
11 KiB
JavaScript
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.

// 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 (von Flask eingebettet) ───────────────────────────
const config = window.config || {}; // Sicherstellen, dass config existiert
// ── Connect-Button ────────────────────────────────────────────────────────
connectBtn.addEventListener('click', async () => {
connectBtn.disabled = true;
connectBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> 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';
console.log("→ Rufe renderChannels() auf");
renderChannels();
console.log("→ renderChannels() abgeschlossen");
} else {
console.warn("→ Connect fehlgeschlagen:", result.message);
alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler'));
}
} catch (err) {
console.error("→ Connect-Fehler:", err);
alert('Netzwerk- oder Verbindungsfehler: ' + err.message);
} finally {
connectBtn.disabled = false;
connectBtn.innerHTML = '<i class="bi bi-bluetooth me-2"></i> 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 = '<span class="spinner-border spinner-border-sm me-2"></span> 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';
alert('Verbindung wiederhergestellt!');
// Optional: Kanäle neu rendern oder Status aktualisieren
} 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 = '<i class="bi bi-arrow-repeat me-2"></i> 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 = '<p class="text-center text-muted py-5">Keine Kanäle in der Konfiguration definiert.</p>';
return;
}
console.log("→ Beginne mit Rendern von", config.channels.length, "Kanälen");
// function renderChannels() {
// if (!channelsContainer) return;
// channelsContainer.innerHTML = '';
// if (!config.channels || config.channels.length === 0) {
// channelsContainer.innerHTML = '<p class="text-center text-muted py-5">Keine Kanäle in der Konfiguration definiert.</p>';
// return;
// }
config.channels.forEach(channel => {
const col = document.createElement('div');
col.className = 'col-md-6 mb-4';
let controlHTML = '';
if (channel.type === 'motor') {
controlHTML = `
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
<input type="range" class="form-range motor-slider"
min="-100" max="100" value="0" step="5"
data-port="${channel.port}">
<div class="d-flex justify-content-between mt-1 small">
<span>-100 %</span>
<span class="value-display fw-bold">0 %</span>
<span>+100 %</span>
</div>
<div class="text-center mt-2">
<button class="btn btn-sm btn-outline-danger stop-channel-btn"
data-port="${channel.port}">
<i class="bi bi-stop-fill me-1"></i> Stop
</button>
</div>
`;
} else {
// Licht, Sound, Fogger etc. → Toggle
controlHTML = `
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
<button class="btn btn-outline-secondary w-100 toggle-btn"
data-port="${channel.port}" data-state="off">
AUS
</button>
`;
}
col.innerHTML = `
<div class="card h-100 shadow-sm">
<div class="card-body">
${controlHTML}
</div>
</div>
`;
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);
// Optional: Hier könnte man showReconnect() aufrufen, wenn gewünscht
// showReconnect();
}
}
// ── Hilfsfunktion: Reconnect-Bereich anzeigen ─────────────────────────────
function showReconnect() {
if (reconnectSection && controlSection) {
controlSection.style.display = 'none';
reconnectSection.style.display = 'block';
}
}
// Optional: Bei Seitenlade-Fehler oder späterem Verbindungsverlust aufrufen
// showReconnect(); // ← zum Testen manuell einblenden
});