diff --git a/src/backend/backend.py b/src/backend/backend.py index 09c3e17e..ff73c10c 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -7,7 +7,7 @@ import os import re import sys import time -from datetime import datetime +from datetime import datetime, timedelta from pathlib import Path from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Union from urllib.parse import urlparse @@ -492,8 +492,13 @@ class Backend: ) - def _get_homeserver_stability(self, logs: List[Dict[str, Any]]) -> float: + def _get_homeserver_stability( + self, logs: List[Dict[str, Any]], + ) -> Tuple[float, List[timedelta]]: + """Return server stability % and a list of downtime durations.""" + stability = 100.0 + durations = [] for period in logs: started_at = datetime.fromtimestamp(period["datetime"]) @@ -502,14 +507,15 @@ class Backend: if time_since_now.days > 30 or period["type"] != 1: # 1 = downtime continue - lasted_minutes = period["duration"] / 60 + lasted_minutes = period["duration"] / 60 + durations.append(timedelta(seconds=period["duration"])) stability -= ( (lasted_minutes * stability / 1000) / max(1, time_since_now.days / 3) ) - return stability + return (stability, durations) async def fetch_homeservers(self) -> None: @@ -533,13 +539,16 @@ class Backend: if server["country"] == "USA": server["country"] = "United States" + stability, durations = \ + self._get_homeserver_stability(server["monitor"]["logs"]) + self.models["homeservers"][homeserver_url] = Homeserver( - id = homeserver_url, - name = server["name"], - site_url = server["url"], - country = server["country"], - stability = - self._get_homeserver_stability(server["monitor"]["logs"]), + id = homeserver_url, + name = server["name"], + site_url = server["url"], + country = server["country"], + stability = stability, + downtimes_ms = [d.total_seconds() * 1000 for d in durations], ) coros.append(self._ping_homeserver(session, homeserver_url)) diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 99054621..07b18b12 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -42,13 +42,14 @@ class PingStatus(AutoStrEnum): class Homeserver(ModelItem): """A homeserver we can connect to. The `id` field is the server's URL.""" - id: str = field() - name: str = field() - site_url: str = field() - country: str = field() - ping: int = -1 - status: PingStatus = PingStatus.Pinging - stability: float = -1 + id: str = field() + name: str = field() + site_url: str = field() + country: str = field() + ping: int = -1 + status: PingStatus = PingStatus.Pinging + stability: float = -1 + downtimes_ms: List[float] = field(default_factory=list) def __lt__(self, other: "Homeserver") -> bool: return (self.name.lower(), self.id) < (other.name.lower(), other.id) diff --git a/src/gui/Pages/AddAccount/ServerDelegate.qml b/src/gui/Pages/AddAccount/ServerDelegate.qml index fec4d764..99687b02 100644 --- a/src/gui/Pages/AddAccount/ServerDelegate.qml +++ b/src/gui/Pages/AddAccount/ServerDelegate.qml @@ -79,6 +79,25 @@ HTile { model.stability >= 95 ? theme.colors.positiveText : model.stability >= 85 ? theme.colors.warningText : theme.colors.errorText + + HoverHandler { id: rightInfoHover } + + HToolTip { + readonly property var times: JSON.parse(model.downtimes_ms) + readonly property real total: utils.sum(times) + + visible: model.stability !== -1 && rightInfoHover.hovered + text: + total === 0 ? + qsTr("No downtimes in the last 30 days") : + qsTr( + "Last 30 days downtimes: %1, average: %2, " + + "longest: %3, total: %4" + ).arg(times.length) + .arg(utils.formatRelativeTime(total / times.length)) + .arg(utils.formatRelativeTime(Math.max.apply(Math,times))) + .arg(utils.formatRelativeTime(total)) + } } HButton {