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}` |
|
| `/event` | POST | Register event: `{"id": "name", "priority": 1-4, "message": "optional", "ttl": seconds}` |
|
||||||
| `/clear` | POST | Clear event: `{"id": "name"}` |
|
| `/clear` | POST | Clear event: `{"id": "name"}` |
|
||||||
|
| `/clear-all` | POST | Clear all active events |
|
||||||
| `/notify` | POST | Notification with optional customization (see below) |
|
| `/notify` | POST | Notification with optional customization (see below) |
|
||||||
| `/sleep` | POST | Enter sleep mode |
|
| `/sleep` | POST | Enter sleep mode |
|
||||||
| `/wake` | POST | Exit sleep mode |
|
| `/wake` | POST | Exit sleep mode |
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ automation:
|
|||||||
| `/events` | GET | List all active events |
|
| `/events` | GET | List all active events |
|
||||||
| `/event` | POST | Register an event |
|
| `/event` | POST | Register an event |
|
||||||
| `/clear` | POST | Clear an event by ID |
|
| `/clear` | POST | Clear an event by ID |
|
||||||
|
| `/clear-all` | POST | Clear all active events |
|
||||||
| `/notify` | POST | Simple notification `{"message": "", "duration": 5}` |
|
| `/notify` | POST | Simple notification `{"message": "", "duration": 5}` |
|
||||||
| `/sleep` | POST | Enter sleep mode |
|
| `/sleep` | POST | Enter sleep mode |
|
||||||
| `/wake` | POST | Exit 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
|
- Occasionally winks `( -_^)` or blinks `( ᵕ.ᵕ)` for a second or two
|
||||||
- Celebrates `\(^o^)/` when recovering from warnings
|
- Celebrates `\(^o^)/` when recovering from warnings
|
||||||
- Each face has its own animation (floating, bouncing, swaying)
|
- 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`):
|
**Sound effects** (tap screen to enable, or use `?sound=on`):
|
||||||
- Warning: soft double-beep
|
- Warning: soft double-beep
|
||||||
|
|||||||
@@ -226,6 +226,17 @@ def post_event():
|
|||||||
return jsonify({"status": "ok", "current_state": state}), 200
|
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"])
|
@app.route("/clear", methods=["POST"])
|
||||||
def clear_event():
|
def clear_event():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ def main():
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
active_alerts = set()
|
active_alerts = set()
|
||||||
|
last_restart_counts = {}
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
containers = get_container_status()
|
containers = get_container_status()
|
||||||
@@ -137,15 +138,23 @@ def main():
|
|||||||
|
|
||||||
event_id = f"docker_{name.replace('/', '_')}"
|
event_id = f"docker_{name.replace('/', '_')}"
|
||||||
|
|
||||||
# Check for restarting state
|
# Check restart count for running/restarting containers
|
||||||
if state == "restarting":
|
# 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)
|
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)")
|
send_event(event_id, 1, f"Container '{name}' restart loop ({restart_count}x)")
|
||||||
current_alerts.add(event_id)
|
current_alerts.add(event_id)
|
||||||
else:
|
elif new_restarts > 0:
|
||||||
send_event(event_id, 2, f"Container '{name}' restarting ({restart_count}x)")
|
send_event(event_id, 2, f"Container '{name}' restarting ({restart_count}x)")
|
||||||
current_alerts.add(event_id)
|
current_alerts.add(event_id)
|
||||||
|
else:
|
||||||
|
print(f"[OK] Container '{name}' is {state}")
|
||||||
|
|
||||||
# Check for exited/dead containers (warning)
|
# Check for exited/dead containers (warning)
|
||||||
elif state in ("exited", "dead"):
|
elif state in ("exited", "dead"):
|
||||||
|
|||||||
@@ -377,6 +377,11 @@
|
|||||||
const prevClass = emoteEl.className;
|
const prevClass = emoteEl.className;
|
||||||
const prevMsg = messageEl.textContent;
|
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!
|
// Surprised face!
|
||||||
emoteEl.textContent = "( °o°)";
|
emoteEl.textContent = "( °o°)";
|
||||||
emoteEl.className = "popping";
|
emoteEl.className = "popping";
|
||||||
|
|||||||
30
openapi.yaml
30
openapi.yaml
@@ -14,7 +14,7 @@ info:
|
|||||||
## TTL/Heartbeat Pattern
|
## TTL/Heartbeat Pattern
|
||||||
Events can have a TTL (time-to-live) that auto-expires them. Detectors typically send
|
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.
|
heartbeat events that expire if not refreshed, indicating loss of communication.
|
||||||
version: 1.3.0
|
version: 1.4.0
|
||||||
license:
|
license:
|
||||||
name: MIT
|
name: MIT
|
||||||
|
|
||||||
@@ -65,6 +65,21 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Error"
|
$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:
|
/clear:
|
||||||
post:
|
post:
|
||||||
summary: Clear an event
|
summary: Clear an event
|
||||||
@@ -233,6 +248,19 @@ components:
|
|||||||
current_state:
|
current_state:
|
||||||
$ref: "#/components/schemas/Status"
|
$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:
|
ClearResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
Reference in New Issue
Block a user