Files
patrickandClaude Sonnet 5.5 347f55cf7c
test / pytest (3.11) (push) Canceled after 0s
test / pytest (3.13) (push) Canceled after 0s
feat: Phase 6c baseline, ack, host alarms, outage notice, send limit, privacy mode, UI link
Includes atomic migrations, per-rule reminder semantics, first-run helper script
and the note on the undecided UI chat idea.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 03:06:23 +01:00

71 lines
2.4 KiB
Python

from datetime import datetime
from ..models import KIND_PROBLEM, Event, display_state, is_host_service
MINUTE = 60
HOUR = 3600
DAY = 86400
CLOCK_FORMAT = "%H:%M"
DATE_CLOCK_FORMAT = "%d.%m. %H:%M"
ARROW = "→"
LESS_THAN_MINUTE = "unter 1 min"
HOST_RECOVERY_HEAD = "Host wieder UP"
def format_duration(seconds: int) -> str:
seconds = max(int(seconds), 0)
if seconds < MINUTE:
return LESS_THAN_MINUTE
if seconds < HOUR:
return f"{seconds // MINUTE} min"
if seconds < DAY:
hours, minutes = seconds // HOUR, (seconds % HOUR) // MINUTE
return f"{hours} h {minutes} min" if minutes else f"{hours} h"
days, rest = divmod(seconds, DAY)
hours = rest // HOUR
return f"{days} d {hours} h" if hours else f"{days} d"
def format_clock(ts: int, now: int) -> str:
moment = datetime.fromtimestamp(ts)
same_day = moment.date() == datetime.fromtimestamp(now).date()
return moment.strftime(CLOCK_FORMAT if same_day else DATE_CLOCK_FORMAT)
def _name(event: Event, state: int) -> str:
return display_state(event.service, state)
def state_label(event: Event) -> str:
if event.previous_state is not None and event.previous_state != event.state and event.kind == KIND_PROBLEM:
label = f"{_name(event, event.previous_state)} {ARROW} {_name(event, event.state)}"
else:
label = _name(event, event.state)
return f"Host {label}" if is_host_service(event.service) else label
def since_text(event: Event) -> str:
if event.since_ts is None:
return ""
text = f"seit {format_clock(event.since_ts, event.ts)}"
if event.previous_state is not None and event.previous_duration is not None:
text += f" (vorher {_name(event, event.previous_state)} seit {format_duration(event.previous_duration)})"
return text
def recovery_text(event: Event) -> str:
host = is_host_service(event.service)
text = HOST_RECOVERY_HEAD if host else "wieder OK"
if event.problem_since_ts is not None:
text += f" nach {format_duration(event.ts - event.problem_since_ts)}"
if event.previous_state is not None:
text += f" (zuletzt {_name(event, event.previous_state)})"
return text
def reminder_text(event: Event) -> str:
text = f"weiter {_name(event, event.state)}"
if event.since_ts is not None:
text += f" seit {format_duration(event.ts - event.since_ts)}"
return text