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>
71 lines
2.4 KiB
Python
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
|