diff --git a/fablednetmon/models/__init__.py b/fablednetmon/models/__init__.py index c0704da..74ebdd2 100644 --- a/fablednetmon/models/__init__.py +++ b/fablednetmon/models/__init__.py @@ -1,4 +1,14 @@ from .base import Base from .users import User +from .hosts import Host, ProbeType +from .monitors import PingResult, DnsResult, PingStatus, DnsStatus +from .metrics import PluginMetric +from .alerts import AlertRule, AlertState, AlertEvent, AlertOperator, AlertStateEnum -__all__ = ["Base", "User"] +__all__ = [ + "Base", "User", + "Host", "ProbeType", + "PingResult", "DnsResult", "PingStatus", "DnsStatus", + "PluginMetric", + "AlertRule", "AlertState", "AlertEvent", "AlertOperator", "AlertStateEnum", +] diff --git a/fablednetmon/models/alerts.py b/fablednetmon/models/alerts.py new file mode 100644 index 0000000..e897be5 --- /dev/null +++ b/fablednetmon/models/alerts.py @@ -0,0 +1,81 @@ +from __future__ import annotations +import enum +import uuid +from datetime import datetime, timezone +from sqlalchemy import Boolean, DateTime, Enum, Float, ForeignKey, Integer, String, Text +from sqlalchemy.orm import Mapped, mapped_column +from .base import Base + + +class AlertOperator(str, enum.Enum): + gt = ">" + lt = "<" + gte = ">=" + lte = "<=" + eq = "==" + ne = "!=" + + +class AlertStateEnum(str, enum.Enum): + inactive = "inactive" + pending = "pending" + firing = "firing" + acknowledged = "acknowledged" + resolved = "resolved" + + +class AlertRule(Base): + __tablename__ = "alert_rules" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + name: Mapped[str] = mapped_column(String(128), nullable=False) + source_module: Mapped[str] = mapped_column(String(64), nullable=False) + resource_name: Mapped[str] = mapped_column(String(255), nullable=False) + metric_name: Mapped[str] = mapped_column(String(128), nullable=False) + operator: Mapped[AlertOperator] = mapped_column(Enum(AlertOperator), nullable=False) + threshold: Mapped[float] = mapped_column(Float, nullable=False) + consecutive_failures_required: Mapped[int] = mapped_column(Integer, nullable=False, default=1) + enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + created_by: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) + + +class AlertState(Base): + __tablename__ = "alert_states" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + rule_id: Mapped[str] = mapped_column( + String(36), ForeignKey("alert_rules.id", ondelete="CASCADE"), nullable=False, unique=True + ) + state: Mapped[AlertStateEnum] = mapped_column( + Enum(AlertStateEnum), nullable=False, default=AlertStateEnum.inactive + ) + consecutive_failure_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + last_transition_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) + acknowledged_by: Mapped[str | None] = mapped_column( + String(36), ForeignKey("users.id"), nullable=True + ) + acknowledged_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + + +class AlertEvent(Base): + __tablename__ = "alert_events" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + rule_id: Mapped[str] = mapped_column( + String(36), ForeignKey("alert_rules.id", ondelete="CASCADE"), nullable=False + ) + from_state: Mapped[str] = mapped_column(String(32), nullable=False) + to_state: Mapped[str] = mapped_column(String(32), nullable=False) + metric_value: Mapped[float] = mapped_column(Float, nullable=False) + transitioned_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) + email_sent: Mapped[bool | None] = mapped_column(Boolean, nullable=True) + email_error: Mapped[str | None] = mapped_column(Text, nullable=True) + webhook_sent: Mapped[bool | None] = mapped_column(Boolean, nullable=True) + webhook_error: Mapped[str | None] = mapped_column(Text, nullable=True) diff --git a/fablednetmon/models/hosts.py b/fablednetmon/models/hosts.py new file mode 100644 index 0000000..61ee641 --- /dev/null +++ b/fablednetmon/models/hosts.py @@ -0,0 +1,29 @@ +from __future__ import annotations +import enum +import uuid +from datetime import datetime, timezone +from sqlalchemy import Boolean, DateTime, Enum, Integer, String +from sqlalchemy.orm import Mapped, mapped_column +from .base import Base + + +class ProbeType(str, enum.Enum): + tcp = "tcp" + icmp = "icmp" + + +class Host(Base): + __tablename__ = "hosts" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + name: Mapped[str] = mapped_column(String(128), nullable=False) + address: Mapped[str] = mapped_column(String(255), nullable=False) + probe_type: Mapped[ProbeType] = mapped_column(Enum(ProbeType), nullable=False, default=ProbeType.tcp) + probe_port: Mapped[int] = mapped_column(Integer, nullable=False, default=80) + ping_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + dns_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + dns_expected_ip: Mapped[str | None] = mapped_column(String(255), nullable=True) + poll_interval_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) diff --git a/fablednetmon/models/metrics.py b/fablednetmon/models/metrics.py new file mode 100644 index 0000000..85a8429 --- /dev/null +++ b/fablednetmon/models/metrics.py @@ -0,0 +1,19 @@ +from __future__ import annotations +import uuid +from datetime import datetime, timezone +from sqlalchemy import DateTime, Float, String +from sqlalchemy.orm import Mapped, mapped_column +from .base import Base + + +class PluginMetric(Base): + __tablename__ = "plugin_metrics" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + source_module: Mapped[str] = mapped_column(String(64), nullable=False) + resource_name: Mapped[str] = mapped_column(String(255), nullable=False) + metric_name: Mapped[str] = mapped_column(String(128), nullable=False) + value: Mapped[float] = mapped_column(Float, nullable=False) + recorded_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) diff --git a/fablednetmon/models/monitors.py b/fablednetmon/models/monitors.py new file mode 100644 index 0000000..0864e85 --- /dev/null +++ b/fablednetmon/models/monitors.py @@ -0,0 +1,45 @@ +from __future__ import annotations +import enum +import uuid +from datetime import datetime, timezone +from sqlalchemy import DateTime, Enum, Float, ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column +from .base import Base + + +class PingStatus(str, enum.Enum): + up = "up" + down = "down" + + +class DnsStatus(str, enum.Enum): + resolved = "resolved" + failed = "failed" + + +class PingResult(Base): + __tablename__ = "ping_results" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + host_id: Mapped[str] = mapped_column( + String(36), ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False + ) + probed_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) + status: Mapped[PingStatus] = mapped_column(Enum(PingStatus), nullable=False) + response_time_ms: Mapped[float | None] = mapped_column(Float, nullable=True) + + +class DnsResult(Base): + __tablename__ = "dns_results" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) + host_id: Mapped[str] = mapped_column( + String(36), ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False + ) + resolved_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + ) + status: Mapped[DnsStatus] = mapped_column(Enum(DnsStatus), nullable=False) + resolved_ip: Mapped[str | None] = mapped_column(String(255), nullable=True)