diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2523798..fb681b2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,5 +12,6 @@ jobs: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install --frozen-lockfile + - run: bun run test - run: bun run lint - run: bun run format:check diff --git a/migrations/0010_slippery_mattie_franklin.sql b/migrations/0010_slippery_mattie_franklin.sql new file mode 100644 index 0000000..bccf6d5 --- /dev/null +++ b/migrations/0010_slippery_mattie_franklin.sql @@ -0,0 +1,25 @@ +CREATE TABLE `ai_events` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `kind` text NOT NULL, + `incident_id` integer, + `monitor_id` integer, + `model` text, + `outcome` text NOT NULL, + `reason` text, + `latency_ms` integer, + `prompt_tokens` integer, + `completion_tokens` integer, + `context_preview` text, + `output_preview` text, + `created_at` integer NOT NULL, + FOREIGN KEY (`incident_id`) REFERENCES `incidents`(`id`) ON UPDATE no action ON DELETE set null, + FOREIGN KEY (`monitor_id`) REFERENCES `monitors`(`id`) ON UPDATE no action ON DELETE set null +); +--> statement-breakpoint +CREATE INDEX `ai_events_created_at_idx` ON `ai_events` (`created_at`);--> statement-breakpoint +ALTER TABLE `ai_settings` ADD `autopilot_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE `ai_settings` ADD `autopilot_followup_minutes` integer DEFAULT 15 NOT NULL;--> statement-breakpoint +ALTER TABLE `ai_settings` ADD `autopilot_max_updates` integer DEFAULT 6 NOT NULL;--> statement-breakpoint +ALTER TABLE `ai_settings` ADD `autopilot_advance_status` integer DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE `ai_settings` ADD `autopilot_degraded_incidents` integer DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE `incidents` ADD `kind` text DEFAULT 'down' NOT NULL; \ No newline at end of file diff --git a/migrations/meta/0010_snapshot.json b/migrations/meta/0010_snapshot.json new file mode 100644 index 0000000..3fd0ef9 --- /dev/null +++ b/migrations/meta/0010_snapshot.json @@ -0,0 +1,1464 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "3722898d-5812-4cc0-9466-aea6cb2ffc57", + "prevId": "fdb7ba86-a907-43e1-bcbb-9cf7cbd1cb15", + "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_events": { + "name": "ai_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "context_preview": { + "name": "context_preview", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_preview": { + "name": "output_preview", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "ai_events_created_at_idx": { + "name": "ai_events_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_events_incident_id_incidents_id_fk": { + "name": "ai_events_incident_id_incidents_id_fk", + "tableFrom": "ai_events", + "tableTo": "incidents", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ai_events_monitor_id_monitors_id_fk": { + "name": "ai_events_monitor_id_monitors_id_fk", + "tableFrom": "ai_events", + "tableTo": "monitors", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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 + }, + "autopilot_enabled": { + "name": "autopilot_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "autopilot_followup_minutes": { + "name": "autopilot_followup_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 15 + }, + "autopilot_max_updates": { + "name": "autopilot_max_updates", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "autopilot_advance_status": { + "name": "autopilot_advance_status", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "autopilot_degraded_incidents": { + "name": "autopilot_degraded_incidents", + "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": {} + }, + "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 + }, + "degraded": { + "name": "degraded", + "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'" + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'down'" + }, + "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 + }, + "expect_keyword": { + "name": "expect_keyword", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "keyword_inverted": { + "name": "keyword_inverted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "request_headers": { + "name": "request_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_body": { + "name": "request_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "degraded_latency_ms": { + "name": "degraded_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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 + }, + "consecutive_slow": { + "name": "consecutive_slow", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_ok": { + "name": "last_ok", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_degraded": { + "name": "last_degraded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 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_channel_monitors": { + "name": "notification_channel_monitors", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_channel_monitors_monitor_id_idx": { + "name": "notification_channel_monitors_monitor_id_idx", + "columns": [ + "monitor_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_channel_monitors_channel_id_notification_channels_id_fk": { + "name": "notification_channel_monitors_channel_id_notification_channels_id_fk", + "tableFrom": "notification_channel_monitors", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_channel_monitors_monitor_id_monitors_id_fk": { + "name": "notification_channel_monitors_monitor_id_monitors_id_fk", + "tableFrom": "notification_channel_monitors", + "tableTo": "monitors", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notification_channel_monitors_channel_id_monitor_id_pk": { + "columns": [ + "channel_id", + "monitor_id" + ], + "name": "notification_channel_monitors_channel_id_monitor_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "notify_manual": { + "name": "notify_manual", + "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": { + "notification_channels_enabled_idx": { + "name": "notification_channels_enabled_idx", + "columns": [ + "enabled" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_deliveries": { + "name": "notification_deliveries", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "channel_id": { + "name": "channel_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "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 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_deliveries_channel_id_created_at_idx": { + "name": "notification_deliveries_channel_id_created_at_idx", + "columns": [ + "channel_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_deliveries_channel_id_notification_channels_id_fk": { + "name": "notification_deliveries_channel_id_notification_channels_id_fk", + "tableFrom": "notification_deliveries", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_deliveries_incident_id_incidents_id_fk": { + "name": "notification_deliveries_incident_id_incidents_id_fk", + "tableFrom": "notification_deliveries", + "tableTo": "incidents", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_deliveries_monitor_id_monitors_id_fk": { + "name": "notification_deliveries_monitor_id_monitors_id_fk", + "tableFrom": "notification_deliveries", + "tableTo": "monitors", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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 da385e6..0ab1107 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1788062139636, "tag": "0009_shocking_butterfly", "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1788183346274, + "tag": "0010_slippery_mattie_franklin", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/client/api/settings.ts b/src/client/api/settings.ts index f884458..3fe7bc0 100644 --- a/src/client/api/settings.ts +++ b/src/client/api/settings.ts @@ -5,6 +5,11 @@ export type AiSettings = { enabled: boolean; baseUrl: string | null; model: string | null; + autopilotEnabled: boolean; + autopilotFollowupMinutes: number; + autopilotMaxUpdates: number; + autopilotAdvanceStatus: boolean; + autopilotDegradedIncidents: boolean; apiKeySet: boolean; apiKeyPreview: string | null; createdAt: string | null; @@ -16,6 +21,35 @@ export type AiSettingsInput = { baseUrl: string | null; model: string | null; apiKey?: string | null; + autopilotEnabled: boolean; + autopilotFollowupMinutes: number; + autopilotMaxUpdates: number; + autopilotAdvanceStatus: boolean; + autopilotDegradedIncidents: boolean; +}; + +export type AiEvent = { + id: number; + kind: string; + incidentId: number | null; + monitorId: number | null; + model: string | null; + outcome: string; + reason: string | null; + latencyMs: number | null; + promptTokens: number | null; + completionTokens: number | null; + contextPreview: string | null; + outputPreview: string | null; + createdAt: string; +}; + +export type AiEventSummary = { + total: number; + ok: number; + averageLatencyMs: number | null; + promptTokens: number; + completionTokens: number; }; export function getAiSettings(signal?: AbortSignal) { @@ -32,3 +66,10 @@ export function updateAiSettings(input: AiSettingsInput) { export function testAiSettings() { return postJson<{ ok: true; message: string }>('/api/settings/ai/test'); } + +export function getAiEvents(signal?: AbortSignal) { + return getJson<{ events: AiEvent[]; summary: AiEventSummary }>('/api/settings/ai/events?limit=50', { + signal, + credentials: 'same-origin', + }); +} diff --git a/src/client/components/settings/AiActivityPanel.tsx b/src/client/components/settings/AiActivityPanel.tsx new file mode 100644 index 0000000..bd124d0 --- /dev/null +++ b/src/client/components/settings/AiActivityPanel.tsx @@ -0,0 +1,66 @@ +import { Badge } from '@/components/ui/badge'; +import { Empty, EmptyDescription, EmptyTitle } from '@/components/ui/empty'; +import { useAiEventsQuery } from '../../queries/settings'; + +function formatTokens(value: number) { + return new Intl.NumberFormat(undefined, { notation: value >= 10_000 ? 'compact' : 'standard' }).format(value); +} + +export function AiActivityPanel() { + const query = useAiEventsQuery(); + if (query.isPending) return
Loading AI activity…
; + if (query.isError) { + return ( + + Unable to load AI activity + + ); + } + const { events, summary } = query.data; + const tokenTotal = summary.promptTokens + summary.completionTokens; + return ( +
+
+
+ {summary.total ? Math.round((summary.ok / summary.total) * 100) : 0}% + Success rate +
+
+ {summary.total} + Calls · 7 days +
+
+ {summary.averageLatencyMs === null ? '—' : `${summary.averageLatencyMs} ms`} + Average latency +
+
+ {formatTokens(tokenTotal)} + Total tokens +
+
+ {events.length === 0 ? ( + + No AI activity yet + Attempts will appear here after AI generation or autopilot runs. + + ) : ( +
+ {events.map((event) => ( +
+ + {event.outcome.replaceAll('_', ' ')} + + {event.kind.replaceAll('_', ' ')} + {new Date(event.createdAt).toLocaleString()} + {event.reason ?? event.model ?? '—'} + + {event.latencyMs === null ? '—' : `${event.latencyMs} ms`} ·{' '} + {formatTokens((event.promptTokens ?? 0) + (event.completionTokens ?? 0))} tokens + +
+ ))} +
+ )} +
+ ); +} diff --git a/src/client/pages/SettingsPage.tsx b/src/client/pages/SettingsPage.tsx index dad4431..54c22a1 100644 --- a/src/client/pages/SettingsPage.tsx +++ b/src/client/pages/SettingsPage.tsx @@ -1,5 +1,5 @@ import { type FormEvent, useState } from 'react'; -import { ArrowLeft, BellRing, Sparkles, Wrench } from 'lucide-react'; +import { Activity, ArrowLeft, BellRing, Sparkles, Wrench } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Empty, EmptyTitle } from '@/components/ui/empty'; @@ -7,6 +7,7 @@ import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import type { AiSettings } from '../api/settings'; import { AppHeader } from '../components/AppHeader'; +import { AiActivityPanel } from '../components/settings/AiActivityPanel'; import { MaintenanceWindowsPanel } from '../components/settings/MaintenanceWindowsPanel'; import { NotificationChannelsPanel } from '../components/settings/NotificationChannelsPanel'; import { navigate } from '../lib/router'; @@ -17,6 +18,11 @@ function AiSettingsForm({ settings }: { settings: AiSettings }) { const [apiKey, setApiKey] = useState(''); const [model, setModel] = useState(settings.model ?? 'gpt-4o-mini'); const [enabled, setEnabled] = useState(settings.enabled); + const [autopilotEnabled, setAutopilotEnabled] = useState(settings.autopilotEnabled); + const [followupMinutes, setFollowupMinutes] = useState(settings.autopilotFollowupMinutes); + const [maxUpdates, setMaxUpdates] = useState(settings.autopilotMaxUpdates); + const [advanceStatus, setAdvanceStatus] = useState(settings.autopilotAdvanceStatus); + const [degradedIncidents, setDegradedIncidents] = useState(settings.autopilotDegradedIncidents); const updateMutation = useUpdateAiSettingsMutation(); const testMutation = useTestAiSettingsMutation(); @@ -27,6 +33,11 @@ function AiSettingsForm({ settings }: { settings: AiSettings }) { baseUrl: baseUrl.trim() || null, model: model.trim() || null, apiKey: apiKey.trim() || null, + autopilotEnabled, + autopilotFollowupMinutes: followupMinutes, + autopilotMaxUpdates: maxUpdates, + autopilotAdvanceStatus: advanceStatus, + autopilotDegradedIncidents: degradedIncidents, }); } @@ -65,6 +76,54 @@ function AiSettingsForm({ settings }: { settings: AiSettings }) { Generate one sanitized public update when an incident opens. +
+ Autopilot +
+ + +
+
+ + +
+
+ + +
+
+ + +
+