Bump to v1.4.0: tap-to-dismiss, docker restart detection, cleanup thread fix

Add /clear-all endpoint and wire it to the tap handler so tapping the
display dismisses active warnings/critical alerts. Fix docker detector
to track restart count deltas instead of relying on the transient
"restarting" state. Wrap cleanup thread in try/except so it can't die
silently and leave events stuck forever.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 11:54:00 -06:00
parent fa0c16609d
commit c3ceb74ce8
6 changed files with 61 additions and 6 deletions

View File

@@ -75,6 +75,7 @@ All detectors support: `AGGREGATOR_URL`, `CHECK_INTERVAL`, `THRESHOLD_WARNING`,
|----------|--------|-------------|
| `/event` | POST | Register event: `{"id": "name", "priority": 1-4, "message": "optional", "ttl": seconds}` |
| `/clear` | POST | Clear event: `{"id": "name"}` |
| `/clear-all` | POST | Clear all active events |
| `/notify` | POST | Notification with optional customization (see below) |
| `/sleep` | POST | Enter sleep mode |
| `/wake` | POST | Exit sleep mode |

View File

@@ -188,6 +188,7 @@ automation:
| `/events` | GET | List all active events |
| `/event` | POST | Register an event |
| `/clear` | POST | Clear an event by ID |
| `/clear-all` | POST | Clear all active events |
| `/notify` | POST | Simple notification `{"message": "", "duration": 5}` |
| `/sleep` | POST | Enter sleep mode |
| `/wake` | POST | Exit sleep mode |
@@ -203,7 +204,7 @@ The emote has personality! In optimal state it:
- Occasionally winks `( -_^)` or blinks `( ᵕ.ᵕ)` for a second or two
- Celebrates `\(^o^)/` when recovering from warnings
- Each face has its own animation (floating, bouncing, swaying)
- Reacts when tapped `( °o°)` and shows version info
- Reacts when tapped `( °o°)`, shows version info, and dismisses active alerts
**Sound effects** (tap screen to enable, or use `?sound=on`):
- Warning: soft double-beep

View File

@@ -226,6 +226,17 @@ def post_event():
return jsonify({"status": "ok", "current_state": state}), 200
@app.route("/clear-all", methods=["POST"])
def clear_all_events():
"""Clear all active events."""
with events_lock:
count = len(active_events)
active_events.clear()
state = write_status()
return jsonify({"status": "cleared", "count": count, "current_state": state}), 200
@app.route("/clear", methods=["POST"])
def clear_event():
"""

View File

@@ -115,6 +115,7 @@ def main():
print()
active_alerts = set()
last_restart_counts = {}
while True:
containers = get_container_status()
@@ -137,15 +138,23 @@ def main():
event_id = f"docker_{name.replace('/', '_')}"
# Check for restarting state
if state == "restarting":
# Check restart count for running/restarting containers
# The "restarting" state is too transient to catch reliably,
# so we track count increases between checks instead
if state in ("running", "restarting"):
restart_count = get_restart_count(name)
if restart_count >= RESTART_THRESHOLD:
prev_count = last_restart_counts.get(name, restart_count)
new_restarts = restart_count - prev_count
last_restart_counts[name] = restart_count
if state == "restarting" or new_restarts >= RESTART_THRESHOLD:
send_event(event_id, 1, f"Container '{name}' restart loop ({restart_count}x)")
current_alerts.add(event_id)
else:
elif new_restarts > 0:
send_event(event_id, 2, f"Container '{name}' restarting ({restart_count}x)")
current_alerts.add(event_id)
else:
print(f"[OK] Container '{name}' is {state}")
# Check for exited/dead containers (warning)
elif state in ("exited", "dead"):

View File

@@ -377,6 +377,11 @@
const prevClass = emoteEl.className;
const prevMsg = messageEl.textContent;
// Clear active warning/critical events
if (lastData && lastData.active_events && lastData.active_events.length > 0) {
fetch("/clear-all", { method: "POST" });
}
// Surprised face!
emoteEl.textContent = "( °o°)";
emoteEl.className = "popping";

View File

@@ -14,7 +14,7 @@ info:
## TTL/Heartbeat Pattern
Events can have a TTL (time-to-live) that auto-expires them. Detectors typically send
heartbeat events that expire if not refreshed, indicating loss of communication.
version: 1.3.0
version: 1.4.0
license:
name: MIT
@@ -65,6 +65,21 @@ paths:
schema:
$ref: "#/components/schemas/Error"
/clear-all:
post:
summary: Clear all events
description: |
Clear all active events at once. Used by the frontend when the display
is tapped to dismiss warnings and critical alerts.
operationId: clearAllEvents
responses:
"200":
description: All events cleared
content:
application/json:
schema:
$ref: "#/components/schemas/ClearAllResponse"
/clear:
post:
summary: Clear an event
@@ -233,6 +248,19 @@ components:
current_state:
$ref: "#/components/schemas/Status"
ClearAllResponse:
type: object
properties:
status:
type: string
example: "cleared"
count:
type: integer
description: Number of events that were cleared
example: 2
current_state:
$ref: "#/components/schemas/Status"
ClearResponse:
type: object
properties: