feat: background device status check every 240s instead of on page load
- Add online, leds, version, last_check columns to wled_devices table - Background job checks all WLED devices every 4 minutes - Initial check on startup - Devices page reads status from DB (instant page load) - 'Alle jetzt pruefen' button triggers manual check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a7efb12d5c
commit
e88ab3ab08
24
app.py
24
app.py
@ -38,9 +38,13 @@ async def lifespan(application: FastAPI):
|
|||||||
# Start background polling
|
# Start background polling
|
||||||
interval = int(database.get_setting("poll_interval", "15"))
|
interval = int(database.get_setting("poll_interval", "15"))
|
||||||
bg_scheduler.add_job(scheduler.poll_status, "interval", seconds=interval, id="poll")
|
bg_scheduler.add_job(scheduler.poll_status, "interval", seconds=interval, id="poll")
|
||||||
|
bg_scheduler.add_job(scheduler.check_devices, "interval", seconds=240, id="device_check")
|
||||||
bg_scheduler.start()
|
bg_scheduler.start()
|
||||||
logger.info("Polling gestartet (Intervall: %ds)", interval)
|
logger.info("Polling gestartet (Intervall: %ds)", interval)
|
||||||
|
|
||||||
|
# Initial device check
|
||||||
|
scheduler.check_devices()
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
bg_scheduler.shutdown()
|
bg_scheduler.shutdown()
|
||||||
@ -195,8 +199,13 @@ async def devices_page(request: Request):
|
|||||||
devices = conn.execute(
|
devices = conn.execute(
|
||||||
"SELECT * FROM wled_devices ORDER BY name, hostname"
|
"SELECT * FROM wled_devices ORDER BY name, hostname"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
last_check_row = conn.execute(
|
||||||
|
"SELECT MAX(last_check) as lc FROM wled_devices WHERE last_check IS NOT NULL"
|
||||||
|
).fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
return templates.TemplateResponse(*_ctx(request, "devices", devices=devices))
|
last_check = last_check_row["lc"] if last_check_row and last_check_row["lc"] else None
|
||||||
|
return templates.TemplateResponse(*_ctx(request, "devices",
|
||||||
|
devices=devices, last_check=last_check))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/devices")
|
@app.post("/api/devices")
|
||||||
@ -241,15 +250,10 @@ async def delete_device(device_id: int):
|
|||||||
return RedirectResponse("/devices", status_code=303)
|
return RedirectResponse("/devices", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/devices/{device_id}/status")
|
@app.post("/api/devices/check-all")
|
||||||
async def device_status(device_id: int):
|
async def check_all_devices():
|
||||||
from wled_client import get_wled_info
|
scheduler.check_devices()
|
||||||
conn = database.get_db()
|
return RedirectResponse("/devices", status_code=303)
|
||||||
dev = conn.execute("SELECT hostname FROM wled_devices WHERE id=?", (device_id,)).fetchone()
|
|
||||||
conn.close()
|
|
||||||
if not dev:
|
|
||||||
return {"online": False}
|
|
||||||
return get_wled_info(dev["hostname"])
|
|
||||||
|
|
||||||
|
|
||||||
# ── Assignments ────────────────────────────────────────────────────────────
|
# ── Assignments ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
24
database.py
24
database.py
@ -32,7 +32,11 @@ def init_db():
|
|||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
hostname TEXT NOT NULL UNIQUE,
|
hostname TEXT NOT NULL UNIQUE,
|
||||||
name TEXT DEFAULT '',
|
name TEXT DEFAULT '',
|
||||||
segment_count INTEGER NOT NULL DEFAULT 2
|
segment_count INTEGER NOT NULL DEFAULT 2,
|
||||||
|
online INTEGER DEFAULT 0,
|
||||||
|
leds INTEGER DEFAULT 0,
|
||||||
|
version TEXT DEFAULT '',
|
||||||
|
last_check TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS assignments (
|
CREATE TABLE IF NOT EXISTS assignments (
|
||||||
@ -71,6 +75,24 @@ def init_db():
|
|||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Migrate: add columns if missing (for existing databases)
|
||||||
|
try:
|
||||||
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN online INTEGER DEFAULT 0")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN leds INTEGER DEFAULT 0")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN version TEXT DEFAULT ''")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN last_check TIMESTAMP")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
"azure_client_id": "",
|
"azure_client_id": "",
|
||||||
"azure_client_secret": "",
|
"azure_client_secret": "",
|
||||||
|
|||||||
28
scheduler.py
28
scheduler.py
@ -3,7 +3,7 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from database import get_db, get_setting, log_activity
|
from database import get_db, get_setting, log_activity
|
||||||
from wled_client import build_wled_url, call_wled
|
from wled_client import build_wled_url, call_wled, get_wled_info
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -197,3 +197,29 @@ def _process_user(user, aida_lines, settings, conn):
|
|||||||
display_name, aida_status or "-", teams_status,
|
display_name, aida_status or "-", teams_status,
|
||||||
"aktualisiert" if wled_changed else "nur Dashboard",
|
"aktualisiert" if wled_changed else "nur Dashboard",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_devices():
|
||||||
|
"""Check all WLED devices and store status in DB. Called periodically."""
|
||||||
|
conn = get_db()
|
||||||
|
try:
|
||||||
|
devices = conn.execute("SELECT id, hostname FROM wled_devices").fetchall()
|
||||||
|
for dev in devices:
|
||||||
|
info = get_wled_info(dev["hostname"])
|
||||||
|
conn.execute("""
|
||||||
|
UPDATE wled_devices
|
||||||
|
SET online=?, leds=?, version=?, last_check=?
|
||||||
|
WHERE id=?
|
||||||
|
""", (
|
||||||
|
1 if info.get("online") else 0,
|
||||||
|
info.get("leds", 0) if info.get("online") else 0,
|
||||||
|
info.get("version", "") if info.get("online") else "",
|
||||||
|
datetime.now(),
|
||||||
|
dev["id"],
|
||||||
|
))
|
||||||
|
conn.commit()
|
||||||
|
logger.info("Device-Check: %d Geraete geprueft", len(devices))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Device-Check Fehler: %s", e)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|||||||
@ -4,9 +4,16 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h4 class="mb-0">WLED-Geraete</h4>
|
<h4 class="mb-0">WLED-Geraete</h4>
|
||||||
<button class="btn btn-sm btn-outline-secondary" onclick="checkAllDevices()">
|
<div>
|
||||||
<i class="bi bi-arrow-clockwise"></i> Alle pruefen
|
<form method="post" action="/api/devices/check-all" class="d-inline">
|
||||||
</button>
|
<button type="submit" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i> Alle jetzt pruefen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% if last_check %}
|
||||||
|
<small class="text-muted ms-2">Letzte Pruefung: {{ last_check }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
@ -30,20 +37,24 @@
|
|||||||
{% for device in devices %}
|
{% for device in devices %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span class="status-dot" id="status-{{ device.id }}"
|
{% if device.last_check %}
|
||||||
style="background: #999;" title="Nicht geprueft"></span>
|
{% if device.online %}
|
||||||
|
<span class="status-dot" style="background: #28a745;" title="Online"></span>
|
||||||
|
{% else %}
|
||||||
|
<span class="status-dot" style="background: #dc3545;" title="Offline"></span>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="status-dot" style="background: #999;" title="Noch nicht geprueft"></span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<code>{{ device.hostname }}</code>
|
<code>{{ device.hostname }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ device.name }}</td>
|
<td>{{ device.name }}</td>
|
||||||
<td><span id="leds-{{ device.id }}" class="text-muted">-</span></td>
|
<td>{{ device.leds if device.leds else '-' }}</td>
|
||||||
<td><span id="version-{{ device.id }}" class="text-muted">-</span></td>
|
<td>{{ device.version if device.version else '-' }}</td>
|
||||||
<td>{{ device.segment_count }}</td>
|
<td>{{ device.segment_count }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-secondary" onclick="checkDevice({{ device.id }})" title="Status pruefen">
|
|
||||||
<i class="bi bi-wifi"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal"
|
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal"
|
||||||
data-bs-target="#edit-{{ device.id }}">
|
data-bs-target="#edit-{{ device.id }}">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
@ -106,6 +117,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2 text-muted">
|
||||||
|
<small>{{ devices|length }} Geraete | Status wird alle 4 Minuten automatisch geprueft</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
@ -139,44 +153,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script>
|
|
||||||
const deviceIds = [{% for d in devices %}{{ d.id }}{% if not loop.last %},{% endif %}{% endfor %}];
|
|
||||||
|
|
||||||
function checkDevice(id) {
|
|
||||||
const dot = document.getElementById('status-' + id);
|
|
||||||
const leds = document.getElementById('leds-' + id);
|
|
||||||
const ver = document.getElementById('version-' + id);
|
|
||||||
dot.style.background = '#ffc107';
|
|
||||||
dot.title = 'Pruefe...';
|
|
||||||
|
|
||||||
fetch('/api/devices/' + id + '/status')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.online) {
|
|
||||||
dot.style.background = '#28a745';
|
|
||||||
dot.title = 'Online';
|
|
||||||
leds.textContent = data.leds || '-';
|
|
||||||
ver.textContent = data.version || '-';
|
|
||||||
} else {
|
|
||||||
dot.style.background = '#dc3545';
|
|
||||||
dot.title = 'Offline';
|
|
||||||
leds.textContent = '-';
|
|
||||||
ver.textContent = '-';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
dot.style.background = '#dc3545';
|
|
||||||
dot.title = 'Fehler';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkAllDevices() {
|
|
||||||
deviceIds.forEach(id => checkDevice(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-check on page load
|
|
||||||
checkAllDevices();
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user