Archived
Refactor UI assets and harden quality checks
This commit is contained in:
1 parent
23ab466c52
commit
4ecc8eb874
62 files changed
+3314
-3206
No files matched your search
@@ -9,6 +9,7 @@ from .types import AccountRow, account_row
|
||||
|
||||
class AccountsMixin(StoreConnectionMixin):
|
||||
def create_account(self, hostname: str, username: str | None = None, *, accepted: bool = True) -> AccountRow:
|
||||
_ = username
|
||||
now = datetime.now(UTC).isoformat()
|
||||
update_slug = new_bearer_secret()
|
||||
management_slug = new_bearer_secret()
|
||||
@@ -27,6 +28,7 @@ class AccountsMixin(StoreConnectionMixin):
|
||||
"update_slug": update_slug,
|
||||
"management_slug": management_slug,
|
||||
"accepted_at": accepted_at,
|
||||
"disabled": 0,
|
||||
}
|
||||
|
||||
def list_accounts(self, limit: int = 100, offset: int = 0) -> list[AccountRow]:
|
||||
@@ -34,7 +36,8 @@ class AccountsMixin(StoreConnectionMixin):
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT accounts.hostname, accounts.update_slug,
|
||||
accounts.created_at, accounts.accepted_at, accounts.disabled, records.ipv4, records.ipv6, records.updated_at
|
||||
accounts.created_at, accounts.accepted_at, accounts.disabled,
|
||||
records.ipv4, records.ipv6, records.updated_at
|
||||
FROM accounts
|
||||
LEFT JOIN records ON records.hostname = accounts.hostname
|
||||
ORDER BY accounts.hostname
|
||||
@@ -54,7 +57,8 @@ class AccountsMixin(StoreConnectionMixin):
|
||||
account = conn.execute(
|
||||
"""
|
||||
SELECT accounts.hostname, accounts.update_slug, accounts.dashboard_password_hash,
|
||||
accounts.created_at, accounts.accepted_at, accounts.disabled, records.ipv4, records.ipv6, records.updated_at
|
||||
accounts.created_at, accounts.accepted_at, accounts.disabled,
|
||||
records.ipv4, records.ipv6, records.updated_at
|
||||
FROM accounts
|
||||
LEFT JOIN records ON records.hostname = accounts.hostname
|
||||
WHERE accounts.hostname = ?
|
||||
@@ -79,7 +83,8 @@ class AccountsMixin(StoreConnectionMixin):
|
||||
account = conn.execute(
|
||||
"""
|
||||
SELECT accounts.hostname, accounts.update_slug, accounts.dashboard_password_hash,
|
||||
accounts.created_at, accounts.accepted_at, accounts.disabled, records.ipv4, records.ipv6, records.updated_at
|
||||
accounts.created_at, accounts.accepted_at, accounts.disabled,
|
||||
records.ipv4, records.ipv6, records.updated_at
|
||||
FROM accounts
|
||||
LEFT JOIN records ON records.hostname = accounts.hostname
|
||||
WHERE accounts.management_hash = ?
|
||||
|
||||
+30
-13
@@ -5,6 +5,29 @@ from datetime import UTC, datetime
|
||||
from .base import StoreConnectionMixin
|
||||
|
||||
|
||||
def _update_metric_increments(
|
||||
latency_ms: float,
|
||||
*,
|
||||
is_success: bool,
|
||||
is_dns_failure: bool,
|
||||
is_reconcile_failure: bool,
|
||||
is_bad_ip: bool,
|
||||
) -> dict[str, float]:
|
||||
increments: dict[str, float] = {
|
||||
"update_requests_total": 1,
|
||||
"update_latency_ms_total": latency_ms,
|
||||
"update_latency_samples_total": 1,
|
||||
}
|
||||
optional_increments = {
|
||||
"update_success_total": is_success,
|
||||
"dns_failures_total": is_dns_failure,
|
||||
"dns_reconcile_failures_total": is_reconcile_failure,
|
||||
"bad_ip_total": is_bad_ip,
|
||||
}
|
||||
increments.update({name: 1 for name, enabled in optional_increments.items() if enabled})
|
||||
return increments
|
||||
|
||||
|
||||
class MetricsMixin(StoreConnectionMixin):
|
||||
def increment_metric(self, name: str, amount: float = 1.0) -> None:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
@@ -48,19 +71,13 @@ class MetricsMixin(StoreConnectionMixin):
|
||||
is_reconcile_failure: bool = False,
|
||||
is_bad_ip: bool = False,
|
||||
) -> None:
|
||||
increments = {
|
||||
"update_requests_total": 1,
|
||||
"update_latency_ms_total": latency_ms,
|
||||
"update_latency_samples_total": 1,
|
||||
}
|
||||
if is_success:
|
||||
increments["update_success_total"] = 1
|
||||
if is_dns_failure:
|
||||
increments["dns_failures_total"] = 1
|
||||
if is_reconcile_failure:
|
||||
increments["dns_reconcile_failures_total"] = 1
|
||||
if is_bad_ip:
|
||||
increments["bad_ip_total"] = 1
|
||||
increments = _update_metric_increments(
|
||||
latency_ms,
|
||||
is_success=is_success,
|
||||
is_dns_failure=is_dns_failure,
|
||||
is_reconcile_failure=is_reconcile_failure,
|
||||
is_bad_ip=is_bad_ip,
|
||||
)
|
||||
|
||||
now = datetime.now(UTC).isoformat()
|
||||
with self.connect() as conn:
|
||||
|
||||
@@ -199,9 +199,11 @@ class RateLimitCleanupMixin(StoreConnectionMixin):
|
||||
[
|
||||
(
|
||||
row["hostname"],
|
||||
CLEANUP_PENDING_HOSTNAME_DELETED
|
||||
if row["cleanup_reason"] == "pending"
|
||||
else CLEANUP_UNUSED_HOSTNAME_DELETED,
|
||||
(
|
||||
CLEANUP_PENDING_HOSTNAME_DELETED
|
||||
if row["cleanup_reason"] == "pending"
|
||||
else CLEANUP_UNUSED_HOSTNAME_DELETED
|
||||
),
|
||||
"cleanup deleted abandoned hostname",
|
||||
"127.0.0.1",
|
||||
datetime.now(UTC).isoformat(),
|
||||
|
||||
+93
-42
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from ..models import UpdateResult
|
||||
@@ -8,6 +9,60 @@ from .base import StoreConnectionMixin
|
||||
from .types import RecordRow, UpdateEventRow, record_row, update_event_row
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class UpdateEventInput:
|
||||
hostname: str
|
||||
ipv4: str | None
|
||||
ipv6: str | None
|
||||
status: str
|
||||
detail: str
|
||||
source_ip: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RecordUpdateInput:
|
||||
hostname: str
|
||||
ipv4: str | None
|
||||
ipv6: str | None
|
||||
update_ipv4: bool = True
|
||||
update_ipv6: bool = True
|
||||
|
||||
|
||||
def _update_event_input(event: UpdateEventInput | str, legacy_values: tuple[object, ...]) -> UpdateEventInput:
|
||||
if isinstance(event, UpdateEventInput):
|
||||
return event
|
||||
if len(legacy_values) != 5:
|
||||
raise TypeError("legacy update event values must be ipv4, ipv6, status, detail, source_ip")
|
||||
ipv4, ipv6, status, detail, source_ip = legacy_values
|
||||
return UpdateEventInput(
|
||||
hostname=event,
|
||||
ipv4=str(ipv4) if ipv4 is not None else None,
|
||||
ipv6=str(ipv6) if ipv6 is not None else None,
|
||||
status=str(status),
|
||||
detail=str(detail),
|
||||
source_ip=str(source_ip),
|
||||
)
|
||||
|
||||
|
||||
def _planned_record_update(
|
||||
update: RecordUpdateInput,
|
||||
*,
|
||||
current_ipv4: str | None,
|
||||
current_ipv6: str | None,
|
||||
existing: bool,
|
||||
) -> UpdateResult:
|
||||
effective_ipv4 = update.ipv4 if update.update_ipv4 else current_ipv4
|
||||
effective_ipv6 = update.ipv6 if update.update_ipv6 else current_ipv6
|
||||
return UpdateResult(
|
||||
hostname=update.hostname,
|
||||
ipv4=effective_ipv4,
|
||||
ipv6=effective_ipv6,
|
||||
changed=not existing or current_ipv4 != effective_ipv4 or current_ipv6 != effective_ipv6,
|
||||
update_ipv4=update.update_ipv4,
|
||||
update_ipv6=update.update_ipv6,
|
||||
)
|
||||
|
||||
|
||||
class RecordsEventsMixin(StoreConnectionMixin):
|
||||
def upsert(
|
||||
self,
|
||||
@@ -18,13 +73,17 @@ class RecordsEventsMixin(StoreConnectionMixin):
|
||||
update_ipv6: bool = True,
|
||||
) -> UpdateResult:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
update = RecordUpdateInput(hostname, ipv4, ipv6, update_ipv4, update_ipv6)
|
||||
with self.connect() as conn:
|
||||
existing = conn.execute("SELECT ipv4, ipv6 FROM records WHERE hostname = ?", (hostname,)).fetchone()
|
||||
current_ipv4 = existing["ipv4"] if existing else None
|
||||
current_ipv6 = existing["ipv6"] if existing else None
|
||||
effective_ipv4 = ipv4 if update_ipv4 else current_ipv4
|
||||
effective_ipv6 = ipv6 if update_ipv6 else current_ipv6
|
||||
changed = existing is None or current_ipv4 != effective_ipv4 or current_ipv6 != effective_ipv6
|
||||
result = _planned_record_update(
|
||||
update,
|
||||
current_ipv4=current_ipv4,
|
||||
current_ipv6=current_ipv6,
|
||||
existing=existing is not None,
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO records(hostname, ipv4, ipv6, updated_at)
|
||||
@@ -34,16 +93,9 @@ class RecordsEventsMixin(StoreConnectionMixin):
|
||||
ipv6=excluded.ipv6,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
(hostname, effective_ipv4, effective_ipv6, now),
|
||||
(result.hostname, result.ipv4, result.ipv6, now),
|
||||
)
|
||||
return UpdateResult(
|
||||
hostname=hostname,
|
||||
ipv4=effective_ipv4,
|
||||
ipv6=effective_ipv6,
|
||||
changed=changed,
|
||||
update_ipv4=update_ipv4,
|
||||
update_ipv6=update_ipv6,
|
||||
)
|
||||
return result
|
||||
|
||||
def commit_update_result(self, result: UpdateResult) -> UpdateResult:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
@@ -69,20 +121,16 @@ class RecordsEventsMixin(StoreConnectionMixin):
|
||||
update_ipv4: bool = True,
|
||||
update_ipv6: bool = True,
|
||||
) -> UpdateResult:
|
||||
update = RecordUpdateInput(hostname, ipv4, ipv6, update_ipv4, update_ipv6)
|
||||
with self.connect() as conn:
|
||||
existing = conn.execute("SELECT ipv4, ipv6 FROM records WHERE hostname = ?", (hostname,)).fetchone()
|
||||
current_ipv4 = existing["ipv4"] if existing else None
|
||||
current_ipv6 = existing["ipv6"] if existing else None
|
||||
effective_ipv4 = ipv4 if update_ipv4 else current_ipv4
|
||||
effective_ipv6 = ipv6 if update_ipv6 else current_ipv6
|
||||
changed = existing is None or current_ipv4 != effective_ipv4 or current_ipv6 != effective_ipv6
|
||||
return UpdateResult(
|
||||
hostname=hostname,
|
||||
ipv4=effective_ipv4,
|
||||
ipv6=effective_ipv6,
|
||||
changed=changed,
|
||||
update_ipv4=update_ipv4,
|
||||
update_ipv6=update_ipv6,
|
||||
return _planned_record_update(
|
||||
update,
|
||||
current_ipv4=current_ipv4,
|
||||
current_ipv6=current_ipv6,
|
||||
existing=existing is not None,
|
||||
)
|
||||
|
||||
def list_records(self) -> list[RecordRow]:
|
||||
@@ -106,33 +154,27 @@ class RecordsEventsMixin(StoreConnectionMixin):
|
||||
row = conn.execute("SELECT COUNT(*) FROM records").fetchone()
|
||||
return int(row[0])
|
||||
|
||||
def log_update_event(
|
||||
self,
|
||||
hostname: str,
|
||||
ipv4: str | None,
|
||||
ipv6: str | None,
|
||||
status: str,
|
||||
detail: str,
|
||||
source_ip: str,
|
||||
) -> None:
|
||||
def log_update_event(self, event: UpdateEventInput | str, *legacy_values: object) -> None:
|
||||
event = _update_event_input(event, legacy_values)
|
||||
with self.connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO update_events(hostname, ipv4, ipv6, status, detail, source_ip, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(hostname, ipv4, ipv6, status, detail[:500], source_ip, datetime.now(UTC).isoformat()),
|
||||
(
|
||||
event.hostname,
|
||||
event.ipv4,
|
||||
event.ipv6,
|
||||
event.status,
|
||||
event.detail[:500],
|
||||
event.source_ip,
|
||||
datetime.now(UTC).isoformat(),
|
||||
),
|
||||
)
|
||||
|
||||
def log_update_rollup(
|
||||
self,
|
||||
hostname: str,
|
||||
ipv4: str | None,
|
||||
ipv6: str | None,
|
||||
status: str,
|
||||
detail: str,
|
||||
source_ip: str,
|
||||
) -> None:
|
||||
def log_update_rollup(self, event: UpdateEventInput | str, *legacy_values: object) -> None:
|
||||
event = _update_event_input(event, legacy_values)
|
||||
now = datetime.now(UTC).isoformat()
|
||||
with self.connect() as conn:
|
||||
conn.execute(
|
||||
@@ -145,7 +187,16 @@ class RecordsEventsMixin(StoreConnectionMixin):
|
||||
last_seen_at=excluded.last_seen_at,
|
||||
count=count + 1
|
||||
""",
|
||||
(hostname, ipv4 or "", ipv6 or "", status, detail[:500], source_ip, now, now),
|
||||
(
|
||||
event.hostname,
|
||||
event.ipv4 or "",
|
||||
event.ipv6 or "",
|
||||
event.status,
|
||||
event.detail[:500],
|
||||
event.source_ip,
|
||||
now,
|
||||
now,
|
||||
),
|
||||
)
|
||||
|
||||
def list_update_events(self, limit: int = 100, offset: int = 0) -> list[UpdateEventRow]:
|
||||
|
||||
@@ -186,7 +186,10 @@ def _migrate_accounts_schema(conn: sqlite3.Connection) -> None:
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
INSERT INTO accounts_new(hostname, update_slug, management_hash, dashboard_password_hash, created_at, accepted_at, disabled)
|
||||
INSERT INTO accounts_new(
|
||||
hostname, update_slug, management_hash, dashboard_password_hash,
|
||||
created_at, accepted_at, disabled
|
||||
)
|
||||
SELECT hostname, update_slug, management_hash, dashboard_password_hash, created_at, accepted_at, disabled
|
||||
FROM accounts
|
||||
""")
|
||||
|
||||
+12
-12
@@ -1,22 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, TypedDict, cast
|
||||
from typing import Any, NotRequired, TypedDict, cast
|
||||
|
||||
|
||||
class AccountRow(TypedDict, total=False):
|
||||
class AccountRow(TypedDict):
|
||||
hostname: str
|
||||
update_slug: str
|
||||
management_slug: str
|
||||
dashboard_password_hash: str | None
|
||||
created_at: str
|
||||
accepted_at: str | None
|
||||
disabled: int
|
||||
ipv4: str | None
|
||||
ipv6: str | None
|
||||
updated_at: str | None
|
||||
management_slug: NotRequired[str]
|
||||
dashboard_password_hash: NotRequired[str | None]
|
||||
created_at: NotRequired[str]
|
||||
ipv4: NotRequired[str | None]
|
||||
ipv6: NotRequired[str | None]
|
||||
updated_at: NotRequired[str | None]
|
||||
|
||||
|
||||
class UpdateEventRow(TypedDict, total=False):
|
||||
class UpdateEventRow(TypedDict):
|
||||
hostname: str
|
||||
ipv4: str | None
|
||||
ipv6: str | None
|
||||
@@ -33,9 +33,9 @@ class CleanupRunRow(TypedDict):
|
||||
|
||||
|
||||
class ScheduledTaskRow(TypedDict, total=False):
|
||||
name: str
|
||||
interval_seconds: int
|
||||
next_run_at: str
|
||||
name: NotRequired[str]
|
||||
interval_seconds: NotRequired[int]
|
||||
next_run_at: NotRequired[str]
|
||||
lease_owner: str | None
|
||||
lease_expires_at: str | None
|
||||
last_started_at: str | None
|
||||
|
||||
Reference in new issue
Block a user