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:
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
"""
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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";
|
||||
|
||||
30
openapi.yaml
30
openapi.yaml
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user