feat(bot): show actual response, flap threshold, pause/rename/baseline, komodo fleet and alerts
This commit is contained in:
1 parent
a46bd92b4c
commit
a6968ebbe6
18 files changed
+1105
-103
No files matched your search
@@ -32,6 +32,13 @@ CREATE TABLE IF NOT EXISTS incidents (
|
||||
CREATE INDEX IF NOT EXISTS idx_incidents_service ON incidents(service_id);
|
||||
"""
|
||||
|
||||
MIGRATIONS: tuple[tuple[str, str, str], ...] = (
|
||||
("services", "fail_streak", "INTEGER NOT NULL DEFAULT 0"),
|
||||
("services", "is_paused", "INTEGER NOT NULL DEFAULT 0"),
|
||||
("services", "last_latency_ms", "INTEGER"),
|
||||
("services", "last_checked_at", "TEXT"),
|
||||
)
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, path: str) -> None:
|
||||
@@ -46,10 +53,20 @@ class Database:
|
||||
self._conn.row_factory = aiosqlite.Row
|
||||
await self._conn.execute("PRAGMA foreign_keys = ON")
|
||||
await self._conn.executescript(SCHEMA)
|
||||
await self._migrate()
|
||||
await self._conn.commit()
|
||||
self.services = ServiceRepository(self._conn)
|
||||
self.incidents = IncidentRepository(self._conn)
|
||||
|
||||
async def _migrate(self) -> None:
|
||||
assert self._conn is not None
|
||||
for table, column, decl in MIGRATIONS:
|
||||
cursor = await self._conn.execute(f"PRAGMA table_info({table})")
|
||||
existing = {row["name"] for row in await cursor.fetchall()}
|
||||
if column in existing:
|
||||
continue
|
||||
await self._conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {decl}")
|
||||
|
||||
async def close(self) -> None:
|
||||
if self._conn is not None:
|
||||
await self._conn.close()
|
||||
|
||||
+13
-3
@@ -3,9 +3,9 @@ from enum import StrEnum
|
||||
|
||||
|
||||
class MatchType(StrEnum):
|
||||
STATUS = "status" # следим только за HTTP-статусом
|
||||
BODY = "body" # тело ответа должно совпадать целиком
|
||||
JSON = "json" # конкретное поле JSON должно быть равно эталону
|
||||
STATUS = "status"
|
||||
BODY = "body"
|
||||
JSON = "json"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -20,6 +20,10 @@ class Service:
|
||||
json_value: str | None
|
||||
is_up: bool
|
||||
created_at: str
|
||||
fail_streak: int = 0
|
||||
is_paused: bool = False
|
||||
last_latency_ms: int | None = None
|
||||
last_checked_at: str | None = None
|
||||
|
||||
@property
|
||||
def rule(self) -> str:
|
||||
@@ -29,6 +33,12 @@ class Service:
|
||||
return f"<code>{self.json_path}</code> = <code>{self.json_value}</code>"
|
||||
return "тело ответа неизменно"
|
||||
|
||||
@property
|
||||
def mark(self) -> str:
|
||||
if self.is_paused:
|
||||
return "⏸"
|
||||
return "🟢" if self.is_up else "🔴"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Incident:
|
||||
|
||||
@@ -5,7 +5,8 @@ from utils.format import now
|
||||
|
||||
_COLUMNS = (
|
||||
"id, name, url, match_type, ok_status, ok_body, "
|
||||
"json_path, json_value, is_up, created_at"
|
||||
"json_path, json_value, is_up, created_at, "
|
||||
"fail_streak, is_paused, last_latency_ms, last_checked_at"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +22,10 @@ def _row_to_service(row: aiosqlite.Row) -> Service:
|
||||
json_value=row["json_value"],
|
||||
is_up=bool(row["is_up"]),
|
||||
created_at=row["created_at"],
|
||||
fail_streak=row["fail_streak"],
|
||||
is_paused=bool(row["is_paused"]),
|
||||
last_latency_ms=row["last_latency_ms"],
|
||||
last_checked_at=row["last_checked_at"],
|
||||
)
|
||||
|
||||
|
||||
@@ -87,6 +92,43 @@ class ServiceRepository:
|
||||
)
|
||||
await self._conn.commit()
|
||||
|
||||
async def set_streak(self, service_id: int, streak: int) -> None:
|
||||
await self._conn.execute(
|
||||
"UPDATE services SET fail_streak = ? WHERE id = ?", (streak, service_id)
|
||||
)
|
||||
await self._conn.commit()
|
||||
|
||||
async def set_paused(self, service_id: int, *, is_paused: bool) -> None:
|
||||
await self._conn.execute(
|
||||
"UPDATE services SET is_paused = ?, fail_streak = 0 WHERE id = ?",
|
||||
(int(is_paused), service_id),
|
||||
)
|
||||
await self._conn.commit()
|
||||
|
||||
async def touch_probe(self, service_id: int, latency_ms: int | None) -> None:
|
||||
await self._conn.execute(
|
||||
"UPDATE services SET last_latency_ms = ?, last_checked_at = ? WHERE id = ?",
|
||||
(latency_ms, now().isoformat(), service_id),
|
||||
)
|
||||
await self._conn.commit()
|
||||
|
||||
async def rename(self, service_id: int, name: str) -> None:
|
||||
await self._conn.execute(
|
||||
"UPDATE services SET name = ? WHERE id = ?", (name, service_id)
|
||||
)
|
||||
await self._conn.commit()
|
||||
|
||||
async def set_baseline(
|
||||
self, service_id: int, *, ok_status: int, ok_body: str, json_value: str | None
|
||||
) -> None:
|
||||
"""Переснять эталон с текущего ответа."""
|
||||
await self._conn.execute(
|
||||
"UPDATE services SET ok_status = ?, ok_body = ?, json_value = ? "
|
||||
"WHERE id = ?",
|
||||
(ok_status, ok_body, json_value, service_id),
|
||||
)
|
||||
await self._conn.commit()
|
||||
|
||||
async def delete(self, service_id: int) -> None:
|
||||
await self._conn.execute("DELETE FROM services WHERE id = ?", (service_id,))
|
||||
await self._conn.commit()
|
||||
Reference in new issue
Block a user