diff --git a/migrations/0007_true_mauler.sql b/migrations/0007_true_mauler.sql
new file mode 100644
index 0000000..64da692
--- /dev/null
+++ b/migrations/0007_true_mauler.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `monitors` ADD `retry_count` integer DEFAULT 1 NOT NULL;--> statement-breakpoint
+ALTER TABLE `monitors` ADD `failure_threshold` integer DEFAULT 2 NOT NULL;--> statement-breakpoint
+ALTER TABLE `monitors` ADD `consecutive_failures` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
+UPDATE `monitors` SET `consecutive_failures` = `failure_threshold` WHERE `last_ok` = 0;
diff --git a/migrations/meta/0007_snapshot.json b/migrations/meta/0007_snapshot.json
new file mode 100644
index 0000000..d185312
--- /dev/null
+++ b/migrations/meta/0007_snapshot.json
@@ -0,0 +1,993 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "3e14e2ff-5a06-45c1-83c8-91d471c8cdf7",
+ "prevId": "6e3277e0-8fe4-4619-8cb4-52c7c5185416",
+ "tables": {
+ "admin_credentials": {
+ "name": "admin_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ai_settings": {
+ "name": "ai_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "base_url": {
+ "name": "base_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "api_key": {
+ "name": "api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "checks": {
+ "name": "checks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ok": {
+ "name": "ok",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status_code": {
+ "name": "status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "checked_at": {
+ "name": "checked_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "maintenance": {
+ "name": "maintenance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {
+ "checks_monitor_id_checked_at_idx": {
+ "name": "checks_monitor_id_checked_at_idx",
+ "columns": [
+ "monitor_id",
+ "checked_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "checks_monitor_id_monitors_id_fk": {
+ "name": "checks_monitor_id_monitors_id_fk",
+ "tableFrom": "checks",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "incident_monitors": {
+ "name": "incident_monitors",
+ "columns": {
+ "incident_id": {
+ "name": "incident_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "incident_monitors_monitor_id_idx": {
+ "name": "incident_monitors_monitor_id_idx",
+ "columns": [
+ "monitor_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "incident_monitors_incident_id_incidents_id_fk": {
+ "name": "incident_monitors_incident_id_incidents_id_fk",
+ "tableFrom": "incident_monitors",
+ "tableTo": "incidents",
+ "columnsFrom": [
+ "incident_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "incident_monitors_monitor_id_monitors_id_fk": {
+ "name": "incident_monitors_monitor_id_monitors_id_fk",
+ "tableFrom": "incident_monitors",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "incident_monitors_incident_id_monitor_id_pk": {
+ "columns": [
+ "incident_id",
+ "monitor_id"
+ ],
+ "name": "incident_monitors_incident_id_monitor_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "incident_updates": {
+ "name": "incident_updates",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "incident_id": {
+ "name": "incident_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'manual'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "incident_updates_incident_id_created_at_idx": {
+ "name": "incident_updates_incident_id_created_at_idx",
+ "columns": [
+ "incident_id",
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "incident_updates_incident_id_incidents_id_fk": {
+ "name": "incident_updates_incident_id_incidents_id_fk",
+ "tableFrom": "incident_updates",
+ "tableTo": "incidents",
+ "columnsFrom": [
+ "incident_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "incidents": {
+ "name": "incidents",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'investigating'"
+ },
+ "impact": {
+ "name": "impact",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'major'"
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'auto'"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "start_status_code": {
+ "name": "start_status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "start_error": {
+ "name": "start_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "incidents_started_at_idx": {
+ "name": "incidents_started_at_idx",
+ "columns": [
+ "started_at"
+ ],
+ "isUnique": false
+ },
+ "incidents_resolved_at_idx": {
+ "name": "incidents_resolved_at_idx",
+ "columns": [
+ "resolved_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "login_attempts": {
+ "name": "login_attempts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attempted_at": {
+ "name": "attempted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "login_attempts_ip_attempted_at_idx": {
+ "name": "login_attempts_ip_attempted_at_idx",
+ "columns": [
+ "ip_address",
+ "attempted_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "maintenance_window_monitors": {
+ "name": "maintenance_window_monitors",
+ "columns": {
+ "window_id": {
+ "name": "window_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "maintenance_window_monitors_monitor_id_idx": {
+ "name": "maintenance_window_monitors_monitor_id_idx",
+ "columns": [
+ "monitor_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "maintenance_window_monitors_window_id_maintenance_windows_id_fk": {
+ "name": "maintenance_window_monitors_window_id_maintenance_windows_id_fk",
+ "tableFrom": "maintenance_window_monitors",
+ "tableTo": "maintenance_windows",
+ "columnsFrom": [
+ "window_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "maintenance_window_monitors_monitor_id_monitors_id_fk": {
+ "name": "maintenance_window_monitors_monitor_id_monitors_id_fk",
+ "tableFrom": "maintenance_window_monitors",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "maintenance_window_monitors_window_id_monitor_id_pk": {
+ "columns": [
+ "window_id",
+ "monitor_id"
+ ],
+ "name": "maintenance_window_monitors_window_id_monitor_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "maintenance_windows": {
+ "name": "maintenance_windows",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "start_minute": {
+ "name": "start_minute",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "duration_minutes": {
+ "name": "duration_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "maintenance_windows_enabled_idx": {
+ "name": "maintenance_windows_enabled_idx",
+ "columns": [
+ "enabled"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitor_daily_stats": {
+ "name": "monitor_daily_stats",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "day": {
+ "name": "day",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "total_checks": {
+ "name": "total_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "up_checks": {
+ "name": "up_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "avg_latency_ms": {
+ "name": "avg_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "min_latency_ms": {
+ "name": "min_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "max_latency_ms": {
+ "name": "max_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "monitor_daily_stats_monitor_id_day_uidx": {
+ "name": "monitor_daily_stats_monitor_id_day_uidx",
+ "columns": [
+ "monitor_id",
+ "day"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "monitor_daily_stats_monitor_id_monitors_id_fk": {
+ "name": "monitor_daily_stats_monitor_id_monitors_id_fk",
+ "tableFrom": "monitor_daily_stats",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitors": {
+ "name": "monitors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'GET'"
+ },
+ "expected_status": {
+ "name": "expected_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 200
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 300
+ },
+ "timeout_ms": {
+ "name": "timeout_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 10000
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "alerts_enabled": {
+ "name": "alerts_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "retry_count": {
+ "name": "retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "failure_threshold": {
+ "name": "failure_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 2
+ },
+ "consecutive_failures": {
+ "name": "consecutive_failures",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_ok": {
+ "name": "last_ok",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_status_code": {
+ "name": "last_status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_latency_ms": {
+ "name": "last_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_checked_at": {
+ "name": "last_checked_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "monitors_enabled_last_checked_at_idx": {
+ "name": "monitors_enabled_last_checked_at_idx",
+ "columns": [
+ "enabled",
+ "last_checked_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_settings": {
+ "name": "notification_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "webhook_url": {
+ "name": "webhook_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ "expires_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json
index 021f611..c533e85 100644
--- a/migrations/meta/_journal.json
+++ b/migrations/meta/_journal.json
@@ -50,6 +50,13 @@
"when": 1788012122846,
"tag": "0006_gifted_deathstrike",
"breakpoints": true
+ },
+ {
+ "idx": 7,
+ "version": "6",
+ "when": 1788057318682,
+ "tag": "0007_true_mauler",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/client/api/monitors.ts b/src/client/api/monitors.ts
index a116302..3b8737e 100644
--- a/src/client/api/monitors.ts
+++ b/src/client/api/monitors.ts
@@ -12,6 +12,9 @@ export type Monitor = {
timeoutMs: number;
enabled: boolean;
alertsEnabled: boolean;
+ retryCount: number;
+ failureThreshold: number;
+ consecutiveFailures: number;
lastOk: boolean | null;
lastStatusCode: number | null;
lastLatencyMs: number | null;
@@ -28,6 +31,8 @@ export type MonitorInput = {
expectedStatus: number;
intervalSeconds: number;
timeoutMs: number;
+ retryCount?: number;
+ failureThreshold?: number;
enabled?: boolean;
alertsEnabled?: boolean;
};
@@ -37,14 +42,22 @@ export type CheckResult = {
statusCode: number | null;
latencyMs: number;
error: string | null;
+ attempts: number;
};
-export type Check = CheckResult & {
+export type Check = {
id: number;
monitorId: number;
+ ok: boolean;
+ statusCode: number | null;
+ latencyMs: number;
+ error: string | null;
checkedAt: string;
+ maintenance: boolean;
};
+export type CheckTransition = 'opened' | 'pending' | 'cleared' | 'resolved' | null;
+
export type Incident = {
id: number;
title: string | null;
@@ -109,5 +122,5 @@ export function deleteMonitor(id: number) {
}
export function runMonitorCheck(id: number) {
- return postJson<{ result: CheckResult; monitor: Monitor }>(`/api/monitors/${id}/check`);
+ return postJson<{ result: CheckResult; transition: CheckTransition; monitor: Monitor }>(`/api/monitors/${id}/check`);
}
diff --git a/src/client/components/dashboard/DashboardOverview.tsx b/src/client/components/dashboard/DashboardOverview.tsx
index cfa5e33..5896bd6 100644
--- a/src/client/components/dashboard/DashboardOverview.tsx
+++ b/src/client/components/dashboard/DashboardOverview.tsx
@@ -12,6 +12,7 @@ export function DashboardOverview({ onAddMonitor }: DashboardOverviewProps) {
const monitors = monitorsQuery.data?.monitors ?? [];
const up = monitors.filter((monitor) => monitor.lastOk === true).length;
const down = monitors.filter((monitor) => monitor.lastOk === false).length;
+ const degrading = monitors.filter((monitor) => monitor.lastOk !== false && monitor.consecutiveFailures > 0).length;
return (
<>
@@ -42,7 +43,7 @@ export function DashboardOverview({ onAddMonitor }: DashboardOverviewProps) {
Currently down Configuration