365 lines
16 KiB
HTML
365 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}
|
|
Manuelle Tapo-Steuerung | MYP Platform
|
|
{% endblock %}
|
|
|
|
{% block page_heading %}
|
|
<div class="flex items-center space-x-4">
|
|
<div class="bg-gradient-to-br from-red-500 to-orange-600 p-3 rounded-xl shadow-lg">
|
|
<i class="fas fa-tools text-white text-2xl"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-slate-800 dark:text-white">
|
|
Manuelle Tapo-Steuerung
|
|
</h1>
|
|
<p class="text-slate-600 dark:text-slate-300">
|
|
Direkte Kontrolle beliebiger Tapo-Steckdosen (Admin-Bereich)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block page_actions %}
|
|
<div class="flex flex-wrap gap-3">
|
|
<a href="{{ url_for('tapo.tapo_dashboard') }}"
|
|
class="btn-secondary flex items-center space-x-2">
|
|
<i class="fas fa-arrow-left"></i>
|
|
<span>Zurück zur Übersicht</span>
|
|
</a>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<!-- Manuelle Steuerung -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="text-xl font-semibold text-slate-800 dark:text-white flex items-center">
|
|
<i class="fas fa-hand-paper mr-3"></i>
|
|
Manuelle Steuerung
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<form method="POST" action="{{ url_for('tapo.manual_control') }}">
|
|
<div class="space-y-6">
|
|
<!-- IP-Adresse -->
|
|
<div>
|
|
<label for="ip" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
|
<i class="fas fa-network-wired mr-2"></i>IP-Adresse *
|
|
</label>
|
|
<input type="text"
|
|
id="ip"
|
|
name="ip"
|
|
required
|
|
pattern="^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
|
placeholder="192.168.1.100">
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">
|
|
IP-Adresse der Tapo-Steckdose eingeben
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Aktion auswählen -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">
|
|
<i class="fas fa-cog mr-2"></i>Aktion auswählen *
|
|
</label>
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
|
<label class="relative">
|
|
<input type="radio" name="action" value="on" class="sr-only peer" required>
|
|
<div class="p-4 border-2 border-slate-300 dark:border-slate-600 rounded-lg cursor-pointer peer-checked:border-green-500 peer-checked:bg-green-50 dark:peer-checked:bg-green-900/20 transition-all">
|
|
<div class="text-center">
|
|
<i class="fas fa-power-off text-green-600 text-2xl mb-2"></i>
|
|
<div class="font-medium text-slate-800 dark:text-white">Einschalten</div>
|
|
</div>
|
|
</div>
|
|
</label>
|
|
|
|
<label class="relative">
|
|
<input type="radio" name="action" value="off" class="sr-only peer">
|
|
<div class="p-4 border-2 border-slate-300 dark:border-slate-600 rounded-lg cursor-pointer peer-checked:border-slate-500 peer-checked:bg-slate-50 dark:peer-checked:bg-slate-900/20 transition-all">
|
|
<div class="text-center">
|
|
<i class="fas fa-power-off text-slate-600 text-2xl mb-2"></i>
|
|
<div class="font-medium text-slate-800 dark:text-white">Ausschalten</div>
|
|
</div>
|
|
</div>
|
|
</label>
|
|
|
|
<label class="relative">
|
|
<input type="radio" name="action" value="status" class="sr-only peer">
|
|
<div class="p-4 border-2 border-slate-300 dark:border-slate-600 rounded-lg cursor-pointer peer-checked:border-blue-500 peer-checked:bg-blue-50 dark:peer-checked:bg-blue-900/20 transition-all">
|
|
<div class="text-center">
|
|
<i class="fas fa-info-circle text-blue-600 text-2xl mb-2"></i>
|
|
<div class="font-medium text-slate-800 dark:text-white">Status prüfen</div>
|
|
</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<div class="pt-4">
|
|
<button type="submit" class="w-full btn-primary">
|
|
<i class="fas fa-play mr-2"></i>
|
|
Aktion ausführen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="text-xl font-semibold text-slate-800 dark:text-white flex items-center">
|
|
<i class="fas fa-bolt mr-3"></i>
|
|
Schnellaktionen
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<div class="space-y-4">
|
|
<!-- Alle ausschalten -->
|
|
<div class="p-4 border border-orange-300 dark:border-orange-700 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="font-medium text-slate-800 dark:text-white">
|
|
<i class="fas fa-power-off mr-2 text-orange-600"></i>
|
|
Alle Steckdosen ausschalten
|
|
</h3>
|
|
<p class="text-sm text-slate-600 dark:text-slate-400 mt-1">
|
|
Schaltet alle konfigurierten Tapo-Steckdosen aus
|
|
</p>
|
|
</div>
|
|
<button onclick="emergencyShutdown()"
|
|
class="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-lg transition-colors">
|
|
<i class="fas fa-power-off mr-1"></i>
|
|
Alle AUS
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Verbindung testen -->
|
|
<div class="p-4 border border-blue-300 dark:border-blue-700 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="font-medium text-slate-800 dark:text-white">
|
|
<i class="fas fa-network-wired mr-2 text-blue-600"></i>
|
|
Verbindung testen
|
|
</h3>
|
|
<p class="text-sm text-slate-600 dark:text-slate-400 mt-1">
|
|
Testet die IP-Adresse im Eingabefeld
|
|
</p>
|
|
</div>
|
|
<button onclick="testManualConnection()"
|
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
|
|
<i class="fas fa-search mr-1"></i>
|
|
Testen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status aller prüfen -->
|
|
<div class="p-4 border border-green-300 dark:border-green-700 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="font-medium text-slate-800 dark:text-white">
|
|
<i class="fas fa-sync-alt mr-2 text-green-600"></i>
|
|
Status aller prüfen
|
|
</h3>
|
|
<p class="text-sm text-slate-600 dark:text-slate-400 mt-1">
|
|
Aktualisiert den Status aller Steckdosen
|
|
</p>
|
|
</div>
|
|
<button onclick="refreshAllOutlets()"
|
|
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors">
|
|
<i class="fas fa-sync-alt mr-1"></i>
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Warnung Box -->
|
|
<div class="card mt-8">
|
|
<div class="card-body">
|
|
<div class="flex items-start space-x-4 p-4 border border-yellow-400 dark:border-yellow-600 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-exclamation-triangle text-yellow-600 dark:text-yellow-400 text-xl"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-medium text-yellow-800 dark:text-yellow-200 mb-2">
|
|
Wichtige Hinweise zur manuellen Steuerung
|
|
</h3>
|
|
<ul class="text-sm text-yellow-700 dark:text-yellow-300 space-y-1 list-disc list-inside">
|
|
<li>Diese Funktion ist nur für Administratoren verfügbar</li>
|
|
<li>IP-Adressen müssen gültig und erreichbar sein</li>
|
|
<li>Steckdosen müssen mit den globalen Tapo-Anmeldedaten konfiguriert sein</li>
|
|
<li>Alle Aktionen werden protokolliert</li>
|
|
<li>Bei Problemen immer erst die Verbindung testen</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div id="loading-overlay" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
|
<div class="bg-white dark:bg-slate-800 p-6 rounded-lg shadow-lg">
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fas fa-spinner fa-spin text-blue-600 text-xl"></i>
|
|
<span class="text-slate-800 dark:text-white font-medium" id="loading-text">
|
|
Lädt...
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Utility-Funktionen
|
|
function showLoading(text = 'Lädt...') {
|
|
document.getElementById('loading-text').textContent = text;
|
|
document.getElementById('loading-overlay').classList.remove('hidden');
|
|
}
|
|
|
|
function hideLoading() {
|
|
document.getElementById('loading-overlay').classList.add('hidden');
|
|
}
|
|
|
|
// Schnellaktionen
|
|
async function emergencyShutdown() {
|
|
if (!confirm('Möchten Sie wirklich ALLE Tapo-Steckdosen ausschalten?\n\nDies kann alle angeschlossenen Geräte abschalten!')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showLoading('Schalte alle Steckdosen aus...');
|
|
|
|
const response = await fetch('/tapo/all-status');
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.error);
|
|
}
|
|
|
|
let successCount = 0;
|
|
let errorCount = 0;
|
|
|
|
// Alle verfügbaren Steckdosen ausschalten
|
|
for (const [ip, status] of Object.entries(data.outlets)) {
|
|
if (status.reachable) {
|
|
try {
|
|
const controlResponse = await fetch('/tapo/control', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': '{{ csrf_token() }}'
|
|
},
|
|
body: JSON.stringify({ ip, action: 'off' })
|
|
});
|
|
|
|
const controlData = await controlResponse.json();
|
|
if (controlData.success) {
|
|
successCount++;
|
|
} else {
|
|
errorCount++;
|
|
}
|
|
} catch (error) {
|
|
errorCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (successCount > 0) {
|
|
showFlashMessage(`✅ ${successCount} Steckdosen erfolgreich ausgeschaltet${errorCount > 0 ? `, ${errorCount} Fehler` : ''}`, 'success');
|
|
} else {
|
|
showFlashMessage('❌ Keine Steckdosen konnten ausgeschaltet werden', 'error');
|
|
}
|
|
|
|
} catch (error) {
|
|
showFlashMessage('Fehler beim Notaus: ' + error.message, 'error');
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
}
|
|
|
|
async function testManualConnection() {
|
|
const ipInput = document.getElementById('ip');
|
|
const ip = ipInput.value.trim();
|
|
|
|
if (!ip) {
|
|
showFlashMessage('Bitte geben Sie eine IP-Adresse ein', 'error');
|
|
ipInput.focus();
|
|
return;
|
|
}
|
|
|
|
// IP-Validation
|
|
if (!/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip)) {
|
|
showFlashMessage('Ungültige IP-Adresse', 'error');
|
|
ipInput.focus();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showLoading(`Teste Verbindung zu ${ip}...`);
|
|
|
|
const response = await fetch(`/tapo/test/${ip}`, { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const testResult = data.test_result;
|
|
if (testResult.success) {
|
|
showFlashMessage(`✅ Verbindung zu ${ip} erfolgreich!`, 'success');
|
|
} else {
|
|
showFlashMessage(`❌ Verbindung zu ${ip} fehlgeschlagen: ${testResult.error}`, 'error');
|
|
}
|
|
} else {
|
|
showFlashMessage(`Verbindungstest fehlgeschlagen: ${data.error}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
showFlashMessage('Fehler beim Verbindungstest: ' + error.message, 'error');
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
}
|
|
|
|
async function refreshAllOutlets() {
|
|
try {
|
|
showLoading('Aktualisiere Status aller Steckdosen...');
|
|
|
|
const response = await fetch('/tapo/all-status');
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showFlashMessage(`✅ Status von ${data.count} Steckdosen aktualisiert`, 'success');
|
|
} else {
|
|
showFlashMessage('Fehler beim Status-Update: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showFlashMessage('Fehler beim Status-Update: ' + error.message, 'error');
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
}
|
|
|
|
// IP-Eingabe Auto-Format
|
|
document.getElementById('ip').addEventListener('input', function(e) {
|
|
let value = e.target.value.replace(/[^0-9.]/g, '');
|
|
e.target.value = value;
|
|
});
|
|
|
|
// Enter-Taste für Test
|
|
document.getElementById('ip').addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
testManualConnection();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |