From 00c65457a0d5373d930963509e0ecb9bd93f9ded Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 17 Mar 2026 19:43:51 -0400 Subject: [PATCH] feat: migration 0002 for hosts, monitors, metrics, and alert tables --- fablednetmon/migrations/env.py | 1 + .../migrations/versions/0002_core_monitors.py | 140 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 fablednetmon/migrations/versions/0002_core_monitors.py diff --git a/fablednetmon/migrations/env.py b/fablednetmon/migrations/env.py index e4b91ce..14bc91c 100644 --- a/fablednetmon/migrations/env.py +++ b/fablednetmon/migrations/env.py @@ -5,6 +5,7 @@ from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config from alembic import context from fablednetmon.models.base import Base +import fablednetmon.models # noqa: F401 — registers all models in metadata config = context.config if config.config_file_name is not None: diff --git a/fablednetmon/migrations/versions/0002_core_monitors.py b/fablednetmon/migrations/versions/0002_core_monitors.py new file mode 100644 index 0000000..442578d --- /dev/null +++ b/fablednetmon/migrations/versions/0002_core_monitors.py @@ -0,0 +1,140 @@ +"""Core monitors, metrics, and alert tables + +Revision ID: 0002_core_monitors +Revises: 0001_core_initial +Create Date: 2026-03-17 +""" +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa + +revision: str = "0002_core_monitors" +down_revision: Union[str, None] = "0001_core_initial" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # hosts + op.create_table( + "hosts", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("name", sa.String(128), nullable=False), + sa.Column("address", sa.String(255), nullable=False), + sa.Column( + "probe_type", + sa.Enum("tcp", "icmp", name="probetype"), + nullable=False, + server_default="tcp", + ), + sa.Column("probe_port", sa.Integer, nullable=False, server_default="80"), + sa.Column("ping_enabled", sa.Boolean, nullable=False, server_default="true"), + sa.Column("dns_enabled", sa.Boolean, nullable=False, server_default="false"), + sa.Column("dns_expected_ip", sa.String(255), nullable=True), + sa.Column("poll_interval_seconds", sa.Integer, nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + ) + + # ping_results + op.create_table( + "ping_results", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("host_id", sa.String(36), sa.ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False), + sa.Column("probed_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("status", sa.Enum("up", "down", name="pingstatus"), nullable=False), + sa.Column("response_time_ms", sa.Float, nullable=True), + ) + + # dns_results + op.create_table( + "dns_results", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("host_id", sa.String(36), sa.ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False), + sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("status", sa.Enum("resolved", "failed", name="dnsstatus"), nullable=False), + sa.Column("resolved_ip", sa.String(255), nullable=True), + ) + + # plugin_metrics + op.create_table( + "plugin_metrics", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("source_module", sa.String(64), nullable=False), + sa.Column("resource_name", sa.String(255), nullable=False), + sa.Column("metric_name", sa.String(128), nullable=False), + sa.Column("value", sa.Float, nullable=False), + sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False), + ) + + # alert_rules + op.create_table( + "alert_rules", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("name", sa.String(128), nullable=False), + sa.Column("source_module", sa.String(64), nullable=False), + sa.Column("resource_name", sa.String(255), nullable=False), + sa.Column("metric_name", sa.String(128), nullable=False), + sa.Column( + "operator", + sa.Enum(">", "<", ">=", "<=", "==", "!=", name="alertoperator"), + nullable=False, + ), + sa.Column("threshold", sa.Float, nullable=False), + sa.Column("consecutive_failures_required", sa.Integer, nullable=False, server_default="1"), + sa.Column("enabled", sa.Boolean, nullable=False, server_default="true"), + sa.Column("created_by", sa.String(36), sa.ForeignKey("users.id", ondelete="RESTRICT"), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + ) + + # alert_states + op.create_table( + "alert_states", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column( + "rule_id", + sa.String(36), + sa.ForeignKey("alert_rules.id", ondelete="CASCADE"), + nullable=False, + unique=True, + ), + sa.Column( + "state", + sa.Enum("inactive", "pending", "firing", "acknowledged", "resolved", name="alertstateenum"), + nullable=False, + server_default="inactive", + ), + sa.Column("consecutive_failure_count", sa.Integer, nullable=False, server_default="0"), + sa.Column("last_transition_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("acknowledged_by", sa.String(36), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True), + sa.Column("acknowledged_at", sa.DateTime(timezone=True), nullable=True), + ) + + # alert_events + op.create_table( + "alert_events", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("rule_id", sa.String(36), sa.ForeignKey("alert_rules.id", ondelete="CASCADE"), nullable=False), + sa.Column("from_state", sa.String(32), nullable=False), + sa.Column("to_state", sa.String(32), nullable=False), + sa.Column("metric_value", sa.Float, nullable=False), + sa.Column("transitioned_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("email_sent", sa.Boolean, nullable=True), + sa.Column("email_error", sa.Text, nullable=True), + sa.Column("webhook_sent", sa.Boolean, nullable=True), + sa.Column("webhook_error", sa.Text, nullable=True), + ) + + +def downgrade() -> None: + op.drop_table("alert_events") + op.drop_table("alert_states") + op.drop_table("alert_rules") + op.drop_table("plugin_metrics") + op.drop_table("dns_results") + op.drop_table("ping_results") + op.drop_table("hosts") + op.execute("DROP TYPE IF EXISTS alertstateenum") + op.execute("DROP TYPE IF EXISTS alertoperator") + op.execute("DROP TYPE IF EXISTS dnsstatus") + op.execute("DROP TYPE IF EXISTS pingstatus") + op.execute("DROP TYPE IF EXISTS probetype")