diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index a33b38fd1..ab821f300 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -161,7 +161,7 @@ http { root /app/html; index index.html index.htm; expires off; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-6OrZHOmlg8SaaMdXOWFOMsBrosTVDr0JC5JXhs2mSls='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-xKFwPOSyQDCFj701DTxJU9nyOtY2cN1n+z8W2Q5rnUw='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; diff --git a/docker/nginx.conf b/docker/nginx.conf index fd47f2f69..1232cc034 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -142,7 +142,7 @@ http { root /app/html; index index.html index.htm; expires off; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-6OrZHOmlg8SaaMdXOWFOMsBrosTVDr0JC5JXhs2mSls='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-xKFwPOSyQDCFj701DTxJU9nyOtY2cN1n+z8W2Q5rnUw='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; diff --git a/drizzle/mysql/0034_green_vector.sql b/drizzle/mysql/0034_green_vector.sql new file mode 100644 index 000000000..5dcc7bac9 --- /dev/null +++ b/drizzle/mysql/0034_green_vector.sql @@ -0,0 +1,2 @@ +-- dropped, see plugin adoption (fleets plugin, migrations/mysql/0001_adopt_fleets.sql) +SELECT 1; diff --git a/drizzle/mysql/meta/0034_snapshot.json b/drizzle/mysql/meta/0034_snapshot.json new file mode 100644 index 000000000..e73f8955c --- /dev/null +++ b/drizzle/mysql/meta/0034_snapshot.json @@ -0,0 +1,9309 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "af492a8b-2637-42b3-9641-09cf207f1e7f", + "prevId": "a5728e18-ef95-4fb5-b895-4fa0cf48106c", + "tables": { + "ai_conversations": { + "name": "ai_conversations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_conversations_user": { + "name": "idx_ai_conversations_user", + "columns": [ + "user_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_conversations_user_id_users_id_fk": { + "name": "ai_conversations_user_id_users_id_fk", + "tableFrom": "ai_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_conversations_id": { + "name": "ai_conversations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ai_messages": { + "name": "ai_messages", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('')" + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_messages_conversation": { + "name": "idx_ai_messages_conversation", + "columns": [ + "conversation_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_messages_conversation_id_ai_conversations_id_fk": { + "name": "ai_messages_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_messages", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_messages_id": { + "name": "ai_messages_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ai_proposals": { + "name": "ai_proposals", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{}')" + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_proposals_user": { + "name": "idx_ai_proposals_user", + "columns": [ + "user_id", + "status" + ], + "isUnique": false + }, + "idx_ai_proposals_conversation": { + "name": "idx_ai_proposals_conversation", + "columns": [ + "conversation_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_proposals_conversation_id_ai_conversations_id_fk": { + "name": "ai_proposals_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_proposals_user_id_users_id_fk": { + "name": "ai_proposals_user_id_users_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_proposals_id": { + "name": "ai_proposals_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ai_providers": { + "name": "ai_providers", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": 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 + }, + "api_key_prefix": { + "name": "api_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_providers_user_label": { + "name": "idx_ai_providers_user_label", + "columns": [ + "user_id", + "label" + ], + "isUnique": true + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_providers_id": { + "name": "ai_providers_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "api_keys_id": { + "name": "api_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audit_logs_id": { + "name": "audit_logs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_channels": { + "name": "automation_channels", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_channels_pair": { + "name": "idx_automation_channels_pair", + "columns": [ + "automation_id", + "channel_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_channels_automation_id_automations_id_fk": { + "name": "automation_channels_automation_id_automations_id_fk", + "tableFrom": "automation_channels", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_channels_channel_id_notification_channels_id_fk": { + "name": "automation_channels_channel_id_notification_channels_id_fk", + "tableFrom": "automation_channels", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_channels_id": { + "name": "automation_channels_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_run_steps": { + "name": "automation_run_steps", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "run_id": { + "name": "run_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_index": { + "name": "step_index", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_type": { + "name": "step_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "truncated": { + "name": "truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_automation_run_steps_run": { + "name": "idx_automation_run_steps_run", + "columns": [ + "run_id", + "step_index" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_run_steps_run_id_automation_runs_id_fk": { + "name": "automation_run_steps_run_id_automation_runs_id_fk", + "tableFrom": "automation_run_steps", + "tableTo": "automation_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_run_steps_id": { + "name": "automation_run_steps_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_runs": { + "name": "automation_runs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_context": { + "name": "trigger_context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_runs_automation": { + "name": "idx_automation_runs_automation", + "columns": [ + "automation_id", + "started_at" + ], + "isUnique": false + }, + "idx_automation_runs_user": { + "name": "idx_automation_runs_user", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_user_id_users_id_fk": { + "name": "automation_runs_user_id_users_id_fk", + "tableFrom": "automation_runs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_runs_id": { + "name": "automation_runs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_schedules": { + "name": "automation_schedules", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_due_at": { + "name": "next_due_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_tick_at": { + "name": "last_tick_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_schedules_automation": { + "name": "idx_automation_schedules_automation", + "columns": [ + "automation_id" + ], + "isUnique": true + }, + "idx_automation_schedules_due": { + "name": "idx_automation_schedules_due", + "columns": [ + "next_due_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_schedules_automation_id_automations_id_fk": { + "name": "automation_schedules_automation_id_automations_id_fk", + "tableFrom": "automation_schedules", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_schedules_id": { + "name": "automation_schedules_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_trigger_state": { + "name": "automation_trigger_state", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state_key": { + "name": "state_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "breach_started_at": { + "name": "breach_started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_value": { + "name": "last_value", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_observed_state": { + "name": "last_observed_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_automation_trigger_state_key": { + "name": "idx_automation_trigger_state_key", + "columns": [ + "automation_id", + "state_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_trigger_state_automation_id_automations_id_fk": { + "name": "automation_trigger_state_automation_id_automations_id_fk", + "tableFrom": "automation_trigger_state", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_trigger_state_id": { + "name": "automation_trigger_state_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "definition_version": { + "name": "definition_version", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('skip')" + }, + "max_run_seconds": { + "name": "max_run_seconds", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_automations_user": { + "name": "idx_automations_user", + "columns": [ + "user_id", + "enabled" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_user_id_users_id_fk": { + "name": "automations_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automations_id": { + "name": "automations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "c2s_tunnel_presets": { + "name": "c2s_tunnel_presets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "computer_name": { + "name": "computer_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "c2s_tunnel_presets_user_id_users_id_fk": { + "name": "c2s_tunnel_presets_user_id_users_id_fk", + "tableFrom": "c2s_tunnel_presets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "c2s_tunnel_presets_id": { + "name": "c2s_tunnel_presets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "collab_room_members": { + "name": "collab_room_members", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "room_id": { + "name": "room_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_role": { + "name": "room_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('member')" + }, + "added_by": { + "name": "added_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_collab_room_members_room_user": { + "name": "idx_collab_room_members_room_user", + "columns": [ + "room_id", + "user_id" + ], + "isUnique": true + }, + "idx_collab_room_members_user": { + "name": "idx_collab_room_members_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "collab_room_members_room_id_collab_rooms_id_fk": { + "name": "collab_room_members_room_id_collab_rooms_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "collab_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_user_id_users_id_fk": { + "name": "collab_room_members_user_id_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_added_by_users_id_fk": { + "name": "collab_room_members_added_by_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collab_room_members_id": { + "name": "collab_room_members_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "collab_rooms": { + "name": "collab_rooms", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "persistent": { + "name": "persistent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "presenter_user_id": { + "name": "presenter_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_protocol": { + "name": "stage_protocol", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_host_id": { + "name": "stage_host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_share_id": { + "name": "stage_share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_link_token": { + "name": "guest_link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_collab_rooms_owner": { + "name": "idx_collab_rooms_owner", + "columns": [ + "owner_user_id" + ], + "isUnique": false + }, + "idx_collab_rooms_guest_token": { + "name": "idx_collab_rooms_guest_token", + "columns": [ + "guest_link_token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "collab_rooms_owner_user_id_users_id_fk": { + "name": "collab_rooms_owner_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_rooms_presenter_user_id_users_id_fk": { + "name": "collab_rooms_presenter_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "presenter_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_host_id_ssh_data_id_fk": { + "name": "collab_rooms_stage_host_id_ssh_data_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "ssh_data", + "columnsFrom": [ + "stage_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_share_id_session_shares_id_fk": { + "name": "collab_rooms_stage_share_id_session_shares_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "session_shares", + "columnsFrom": [ + "stage_share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collab_rooms_id": { + "name": "collab_rooms_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "command_history": { + "name": "command_history", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "executed_at": { + "name": "executed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_command_history_user_host": { + "name": "idx_command_history_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "command_history_user_id_users_id_fk": { + "name": "command_history_user_id_users_id_fk", + "tableFrom": "command_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "command_history_host_id_ssh_data_id_fk": { + "name": "command_history_host_id_ssh_data_id_fk", + "tableFrom": "command_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "command_history_id": { + "name": "command_history_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('use')" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_access_id": { + "name": "credential_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_sidebar_preferences_user_id": { + "name": "credential_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "dashboard_service_links": { + "name": "dashboard_service_links", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_service_links_user_id_users_id_fk": { + "name": "dashboard_service_links_user_id_users_id_fk", + "tableFrom": "dashboard_service_links", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dashboard_service_links_id": { + "name": "dashboard_service_links_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "dashboard_service_links_sync_id_unique": { + "name": "dashboard_service_links_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "dismissed_alerts": { + "name": "dismissed_alerts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dismissed_alerts_id": { + "name": "dismissed_alerts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "file_manager_pinned": { + "name": "file_manager_pinned", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_file_manager_pinned_user": { + "name": "idx_file_manager_pinned_user", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "file_manager_pinned_user_id_users_id_fk": { + "name": "file_manager_pinned_user_id_users_id_fk", + "tableFrom": "file_manager_pinned", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_pinned_host_id_ssh_data_id_fk": { + "name": "file_manager_pinned_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_pinned", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "file_manager_pinned_id": { + "name": "file_manager_pinned_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "file_manager_recent": { + "name": "file_manager_recent", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened": { + "name": "last_opened", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_file_manager_recent_user": { + "name": "idx_file_manager_recent_user", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "file_manager_recent_user_id_users_id_fk": { + "name": "file_manager_recent_user_id_users_id_fk", + "tableFrom": "file_manager_recent", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_recent_host_id_ssh_data_id_fk": { + "name": "file_manager_recent_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "file_manager_recent_id": { + "name": "file_manager_recent_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "file_manager_shortcuts": { + "name": "file_manager_shortcuts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_file_manager_shortcuts_user": { + "name": "idx_file_manager_shortcuts_user", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "file_manager_shortcuts_user_id_users_id_fk": { + "name": "file_manager_shortcuts_user_id_users_id_fk", + "tableFrom": "file_manager_shortcuts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_shortcuts_host_id_ssh_data_id_fk": { + "name": "file_manager_shortcuts_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_shortcuts", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "file_manager_shortcuts_id": { + "name": "file_manager_shortcuts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "folder_access": { + "name": "folder_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('connect')" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + "owner_user_id", + "folder" + ], + "isUnique": false + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "folder_access_id": { + "name": "folder_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "homepage_items": { + "name": "homepage_items", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type_id": { + "name": "type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{}')" + }, + "folder_id": { + "name": "folder_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_homepage_items_user_id": { + "name": "idx_homepage_items_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "homepage_items_user_id_users_id_fk": { + "name": "homepage_items_user_id_users_id_fk", + "tableFrom": "homepage_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "homepage_items_id": { + "name": "homepage_items_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "homepage_items_sync_id_unique": { + "name": "homepage_items_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "homepage_layouts": { + "name": "homepage_layouts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{}')" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "homepage_layouts_user_id_users_id_fk": { + "name": "homepage_layouts_user_id_users_id_fk", + "tableFrom": "homepage_layouts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "homepage_layouts_id": { + "name": "homepage_layouts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "homepage_layouts_user_id_unique": { + "name": "homepage_layouts_user_id_unique", + "columns": [ + "user_id" + ] + } + }, + "checkConstraint": {} + }, + "host_access": { + "name": "host_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('connect')" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_access_id": { + "name": "host_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_health_checks": { + "name": "host_health_checks", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checks": { + "name": "checks", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_host_health_checks_user_host": { + "name": "idx_host_health_checks_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "host_health_checks_user_id_users_id_fk": { + "name": "host_health_checks_user_id_users_id_fk", + "tableFrom": "host_health_checks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_health_checks_host_id_ssh_data_id_fk": { + "name": "host_health_checks_host_id_ssh_data_id_fk", + "tableFrom": "host_health_checks", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_health_checks_id": { + "name": "host_health_checks_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_health_history": { + "name": "host_health_history", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_health_history_user_id_users_id_fk": { + "name": "host_health_history_user_id_users_id_fk", + "tableFrom": "host_health_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_health_history_host_id_ssh_data_id_fk": { + "name": "host_health_history_host_id_ssh_data_id_fk", + "tableFrom": "host_health_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_health_history_id": { + "name": "host_health_history_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_metrics_history": { + "name": "host_metrics_history", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mem_percent": { + "name": "mem_percent", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_rx_bytes": { + "name": "net_rx_bytes", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_tx_bytes": { + "name": "net_tx_bytes", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_metrics_history_host_id_ssh_data_id_fk": { + "name": "host_metrics_history_host_id_ssh_data_id_fk", + "tableFrom": "host_metrics_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_metrics_history_id": { + "name": "host_metrics_history_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_metrics_preferences": { + "name": "host_metrics_preferences", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_host_metrics_prefs_user_host": { + "name": "idx_host_metrics_prefs_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "host_metrics_preferences_user_id_users_id_fk": { + "name": "host_metrics_preferences_user_id_users_id_fk", + "tableFrom": "host_metrics_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_metrics_preferences_host_id_ssh_data_id_fk": { + "name": "host_metrics_preferences_host_id_ssh_data_id_fk", + "tableFrom": "host_metrics_preferences", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_metrics_preferences_id": { + "name": "host_metrics_preferences_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_sidebar_preferences_user_id": { + "name": "host_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_data": { + "name": "ssh_data", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('ssh')" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_warpgate": { + "name": "use_warpgate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_password": { + "name": "autostart_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key": { + "name": "autostart_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key_password": { + "name": "autostart_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_profile_id": { + "name": "vault_profile_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_terminal": { + "name": "enable_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_session_logging": { + "name": "enable_session_logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_session_sharing": { + "name": "allow_session_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_command_history": { + "name": "enable_command_history", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_tunnel": { + "name": "enable_tunnel", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "tunnel_connections": { + "name": "tunnel_connections", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_file_manager": { + "name": "enable_file_manager", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "scp_legacy": { + "name": "scp_legacy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_docker": { + "name": "enable_docker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_web_ui": { + "name": "enable_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_tmux_monitor": { + "name": "enable_tmux_monitor", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_terminal_toolbar": { + "name": "enable_terminal_toolbar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_ai_assistant": { + "name": "enable_ai_assistant", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_terminal_in_sidebar": { + "name": "show_terminal_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_file_manager_in_sidebar": { + "name": "show_file_manager_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_tunnel_in_sidebar": { + "name": "show_tunnel_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_docker_in_sidebar": { + "name": "show_docker_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_server_stats_in_sidebar": { + "name": "show_server_stats_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_path": { + "name": "default_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stats_config": { + "name": "stats_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "docker_config": { + "name": "docker_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "web_ui_config": { + "name": "web_ui_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_proxmox": { + "name": "enable_proxmox", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "proxmox_config": { + "name": "proxmox_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_proxmox_stats": { + "name": "enable_proxmox_stats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "proxmox_stats_config": { + "name": "proxmox_stats_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_config": { + "name": "terminal_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quick_actions": { + "name": "quick_actions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_rdp": { + "name": "enable_rdp", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_vnc": { + "name": "enable_vnc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_telnet": { + "name": "enable_telnet", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "ssh_port": { + "name": "ssh_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "rdp_port": { + "name": "rdp_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 3389 + }, + "vnc_port": { + "name": "vnc_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 5900 + }, + "telnet_port": { + "name": "telnet_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 23 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_security": { + "name": "rdp_security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_ignore_cert": { + "name": "rdp_ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "security": { + "name": "security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ignore_cert": { + "name": "ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "guacamole_config": { + "name": "guacamole_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('sha256')" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_data_id": { + "name": "ssh_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "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": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notification_channels_id": { + "name": "notification_channels_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "opkssh_tokens": { + "name": "opkssh_tokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opkssh_tokens_id": { + "name": "opkssh_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('aggregate-telemetry')" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_install_counts_id": { + "name": "plugin_install_counts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_migrations_id": { + "name": "plugin_migrations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('admin')" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_permission_grants_id": { + "name": "plugin_permission_grants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_registries_id": { + "name": "plugin_registries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_settings_id": { + "name": "plugin_settings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_storage_id": { + "name": "plugin_storage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('available')" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('disabled')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugins_id": { + "name": "plugins_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "proxmox_node_history": { + "name": "proxmox_node_history", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mem_percent": { + "name": "mem_percent", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_rx_bytes": { + "name": "net_rx_bytes", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_tx_bytes": { + "name": "net_tx_bytes", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "proxmox_node_history_host_id_ssh_data_id_fk": { + "name": "proxmox_node_history_host_id_ssh_data_id_fk", + "tableFrom": "proxmox_node_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "proxmox_node_history_id": { + "name": "proxmox_node_history_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "proxmox_stats_preferences": { + "name": "proxmox_stats_preferences", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_proxmox_stats_prefs_user_host": { + "name": "idx_proxmox_stats_prefs_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "proxmox_stats_preferences_user_id_users_id_fk": { + "name": "proxmox_stats_preferences_user_id_users_id_fk", + "tableFrom": "proxmox_stats_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proxmox_stats_preferences_host_id_ssh_data_id_fk": { + "name": "proxmox_stats_preferences_host_id_ssh_data_id_fk", + "tableFrom": "proxmox_stats_preferences", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "proxmox_stats_preferences_id": { + "name": "proxmox_stats_preferences_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "role_name": { + "name": "role_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + "role_name", + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rbac_applied_defaults_id": { + "name": "rbac_applied_defaults_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rbac_known_permissions": { + "name": "rbac_known_permissions", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rbac_known_permissions_id": { + "name": "rbac_known_permissions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "recent_activity": { + "name": "recent_activity", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "recent_activity_id": { + "name": "recent_activity_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "roles": { + "name": "roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "roles_id": { + "name": "roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "columns": [ + "name" + ] + } + }, + "checkConstraint": {} + }, + "secret_sources": { + "name": "secret_sources", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('onepassword-connect')" + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_secret_sources_user": { + "name": "idx_secret_sources_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "secret_sources_user_id_users_id_fk": { + "name": "secret_sources_user_id_users_id_fk", + "tableFrom": "secret_sources", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "secret_sources_id": { + "name": "secret_sources_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "session_recordings": { + "name": "session_recordings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_id": { + "name": "access_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commands": { + "name": "commands", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dangerous_actions": { + "name": "dangerous_actions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recording_path": { + "name": "recording_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('text')" + }, + "terminated_by_owner": { + "name": "terminated_by_owner", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "termination_reason": { + "name": "termination_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_session_recordings_user_started": { + "name": "idx_session_recordings_user_started", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + }, + "idx_session_recordings_host": { + "name": "idx_session_recordings_host", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_recordings_host_id_ssh_data_id_fk": { + "name": "session_recordings_host_id_ssh_data_id_fk", + "tableFrom": "session_recordings", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_recordings_user_id_users_id_fk": { + "name": "session_recordings_user_id_users_id_fk", + "tableFrom": "session_recordings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "session_recordings_access_id_host_access_id_fk": { + "name": "session_recordings_access_id_host_access_id_fk", + "tableFrom": "session_recordings", + "tableTo": "host_access", + "columnsFrom": [ + "access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_recordings_id": { + "name": "session_recordings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "session_share_participants": { + "name": "session_share_participants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "share_id": { + "name": "share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_label": { + "name": "guest_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "left_at": { + "name": "left_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_share_participants_share_id_session_shares_id_fk": { + "name": "session_share_participants_share_id_session_shares_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "session_shares", + "columnsFrom": [ + "share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_share_participants_user_id_users_id_fk": { + "name": "session_share_participants_user_id_users_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_share_participants_id": { + "name": "session_share_participants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "session_shares": { + "name": "session_shares", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_instance_id": { + "name": "tab_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_type": { + "name": "share_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "link_token": { + "name": "link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('read-only')" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_joined_at": { + "name": "last_joined_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "join_count": { + "name": "join_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_session_shares_session_id": { + "name": "idx_session_shares_session_id", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_session_shares_host_id": { + "name": "idx_session_shares_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_shares_host_id_ssh_data_id_fk": { + "name": "session_shares_host_id_ssh_data_id_fk", + "tableFrom": "session_shares", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_owner_user_id_users_id_fk": { + "name": "session_shares_owner_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_target_user_id_users_id_fk": { + "name": "session_shares_target_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_shares_id": { + "name": "session_shares_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "session_shares_link_token_unique": { + "name": "session_shares_link_token_unique", + "columns": [ + "link_token" + ] + } + }, + "checkConstraint": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "settings_key": { + "name": "settings_key", + "columns": [ + "key" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('password')" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_credential_secrets_id": { + "name": "shared_credential_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_auth_overrides_id": { + "name": "shared_host_auth_overrides_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('credential')" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_secrets_id": { + "name": "shared_host_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credential_usage_id": { + "name": "ssh_credential_usage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credentials_id": { + "name": "ssh_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_folders_id": { + "name": "ssh_folders_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "sso_providers_id": { + "name": "sso_providers_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_tombstones_id": { + "name": "sync_tombstones_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "termix_identities": { + "name": "termix_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identities_id": { + "name": "termix_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_ca_id": { + "name": "termix_identity_ca_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('manual')" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_keys_id": { + "name": "termix_identity_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "tmux_session_tags": { + "name": "tmux_session_tags", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_name": { + "name": "session_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "tmux_session_tags_user_id_users_id_fk": { + "name": "tmux_session_tags_user_id_users_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tmux_session_tags_host_id_ssh_data_id_fk": { + "name": "tmux_session_tags_host_id_ssh_data_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "tmux_session_tags_id": { + "name": "tmux_session_tags_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "transfer_recent": { + "name": "transfer_recent", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_host_id": { + "name": "source_host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dest_host_id": { + "name": "dest_host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dest_path": { + "name": "dest_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dest_path_label": { + "name": "dest_path_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_transfer_recent_user": { + "name": "idx_transfer_recent_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "transfer_recent_user_id_users_id_fk": { + "name": "transfer_recent_user_id_users_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transfer_recent_source_host_id_ssh_data_id_fk": { + "name": "transfer_recent_source_host_id_ssh_data_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "source_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transfer_recent_dest_host_id_ssh_data_id_fk": { + "name": "transfer_recent_dest_host_id_ssh_data_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "dest_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "transfer_recent_id": { + "name": "transfer_recent_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "trusted_devices_id": { + "name": "trusted_devices_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ui_preferences_user_id": { + "name": "ui_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_external_identities_id": { + "name": "user_external_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_open_tabs_id": { + "name": "user_open_tabs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_autocomplete": { + "name": "command_autocomplete", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_assistant_enabled": { + "name": "ai_assistant_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_read_only_commands": { + "name": "ai_read_only_commands", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_defaults": { + "name": "rdp_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id": { + "name": "user_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_id": { + "name": "user_roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_second_factors_id": { + "name": "user_second_factors_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('openid email profile')" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "vault_profiles": { + "name": "vault_profiles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "vault_profiles_id": { + "name": "vault_profiles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "vault_tokens": { + "name": "vault_tokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_id": { + "name": "profile_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + "user_id", + "profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "vault_tokens_id": { + "name": "vault_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "webauthn_credentials": { + "name": "webauthn_credentials", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('preferred')" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "webauthn_credentials_id": { + "name": "webauthn_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/mysql/meta/_journal.json b/drizzle/mysql/meta/_journal.json index a651e0bbf..fbd4f7a32 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -239,6 +239,13 @@ "when": 1790169398523, "tag": "0033_clear_anthem", "breakpoints": true + }, + { + "idx": 34, + "version": "5", + "when": 1790176259240, + "tag": "0034_green_vector", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0035_ambitious_klaw.sql b/drizzle/postgres/0035_ambitious_klaw.sql new file mode 100644 index 000000000..84bcf391f --- /dev/null +++ b/drizzle/postgres/0035_ambitious_klaw.sql @@ -0,0 +1,2 @@ +-- dropped, see plugin adoption (fleets plugin, migrations/postgres/0001_adopt_fleets.sql) +SELECT 1; diff --git a/drizzle/postgres/meta/0035_snapshot.json b/drizzle/postgres/meta/0035_snapshot.json new file mode 100644 index 000000000..8e54c3d44 --- /dev/null +++ b/drizzle/postgres/meta/0035_snapshot.json @@ -0,0 +1,9090 @@ +{ + "id": "4af83863-df15-451c-9bf4-5de1f2e2100b", + "prevId": "f93c0f15-a543-4b77-8cd1-173e20225de6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ai_conversations": { + "name": "ai_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_conversations_user": { + "name": "idx_ai_conversations_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_conversations_user_id_users_id_fk": { + "name": "ai_conversations_user_id_users_id_fk", + "tableFrom": "ai_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_messages": { + "name": "ai_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_messages_conversation": { + "name": "idx_ai_messages_conversation", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_messages_conversation_id_ai_conversations_id_fk": { + "name": "ai_messages_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_messages", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_proposals": { + "name": "ai_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_proposals_user": { + "name": "idx_ai_proposals_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_proposals_conversation": { + "name": "idx_ai_proposals_conversation", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_proposals_conversation_id_ai_conversations_id_fk": { + "name": "ai_proposals_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_proposals_user_id_users_id_fk": { + "name": "ai_proposals_user_id_users_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_providers": { + "name": "ai_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_prefix": { + "name": "api_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_providers_user_label": { + "name": "idx_ai_providers_user_label", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_channels": { + "name": "automation_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_automation_channels_pair": { + "name": "idx_automation_channels_pair", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_channels_automation_id_automations_id_fk": { + "name": "automation_channels_automation_id_automations_id_fk", + "tableFrom": "automation_channels", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_channels_channel_id_notification_channels_id_fk": { + "name": "automation_channels_channel_id_notification_channels_id_fk", + "tableFrom": "automation_channels", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_run_steps": { + "name": "automation_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "step_index": { + "name": "step_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "step_type": { + "name": "step_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "truncated": { + "name": "truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_automation_run_steps_run": { + "name": "idx_automation_run_steps_run", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "step_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_run_steps_run_id_automation_runs_id_fk": { + "name": "automation_run_steps_run_id_automation_runs_id_fk", + "tableFrom": "automation_run_steps", + "tableTo": "automation_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_runs": { + "name": "automation_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_context": { + "name": "trigger_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_automation_runs_automation": { + "name": "idx_automation_runs_automation", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_automation_runs_user": { + "name": "idx_automation_runs_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_user_id_users_id_fk": { + "name": "automation_runs_user_id_users_id_fk", + "tableFrom": "automation_runs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_schedules": { + "name": "automation_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_due_at": { + "name": "next_due_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_tick_at": { + "name": "last_tick_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_automation_schedules_automation": { + "name": "idx_automation_schedules_automation", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_automation_schedules_due": { + "name": "idx_automation_schedules_due", + "columns": [ + { + "expression": "next_due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_schedules_automation_id_automations_id_fk": { + "name": "automation_schedules_automation_id_automations_id_fk", + "tableFrom": "automation_schedules", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_trigger_state": { + "name": "automation_trigger_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "breach_started_at": { + "name": "breach_started_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_value": { + "name": "last_value", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "last_observed_state": { + "name": "last_observed_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automation_trigger_state_key": { + "name": "idx_automation_trigger_state_key", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_trigger_state_automation_id_automations_id_fk": { + "name": "automation_trigger_state_automation_id_automations_id_fk", + "tableFrom": "automation_trigger_state", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition_version": { + "name": "definition_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip'" + }, + "max_run_seconds": { + "name": "max_run_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automations_user": { + "name": "idx_automations_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automations_user_id_users_id_fk": { + "name": "automations_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.c2s_tunnel_presets": { + "name": "c2s_tunnel_presets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "computer_name": { + "name": "computer_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "c2s_tunnel_presets_user_id_users_id_fk": { + "name": "c2s_tunnel_presets_user_id_users_id_fk", + "tableFrom": "c2s_tunnel_presets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collab_room_members": { + "name": "collab_room_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "room_role": { + "name": "room_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "added_by": { + "name": "added_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_collab_room_members_room_user": { + "name": "idx_collab_room_members_room_user", + "columns": [ + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_collab_room_members_user": { + "name": "idx_collab_room_members_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collab_room_members_room_id_collab_rooms_id_fk": { + "name": "collab_room_members_room_id_collab_rooms_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "collab_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_user_id_users_id_fk": { + "name": "collab_room_members_user_id_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_added_by_users_id_fk": { + "name": "collab_room_members_added_by_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collab_rooms": { + "name": "collab_rooms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "persistent": { + "name": "persistent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "presenter_user_id": { + "name": "presenter_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "stage_protocol": { + "name": "stage_protocol", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stage_host_id": { + "name": "stage_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stage_share_id": { + "name": "stage_share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "guest_link_token": { + "name": "guest_link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_collab_rooms_owner": { + "name": "idx_collab_rooms_owner", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_collab_rooms_guest_token": { + "name": "idx_collab_rooms_guest_token", + "columns": [ + { + "expression": "guest_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collab_rooms_owner_user_id_users_id_fk": { + "name": "collab_rooms_owner_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_rooms_presenter_user_id_users_id_fk": { + "name": "collab_rooms_presenter_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "presenter_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_host_id_ssh_data_id_fk": { + "name": "collab_rooms_stage_host_id_ssh_data_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "ssh_data", + "columnsFrom": [ + "stage_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_share_id_session_shares_id_fk": { + "name": "collab_rooms_stage_share_id_session_shares_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "session_shares", + "columnsFrom": [ + "stage_share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.command_history": { + "name": "command_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "executed_at": { + "name": "executed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_command_history_user_host": { + "name": "idx_command_history_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "command_history_user_id_users_id_fk": { + "name": "command_history_user_id_users_id_fk", + "tableFrom": "command_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "command_history_host_id_ssh_data_id_fk": { + "name": "command_history_host_id_ssh_data_id_fk", + "tableFrom": "command_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_access": { + "name": "credential_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_service_links": { + "name": "dashboard_service_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_service_links_user_id_users_id_fk": { + "name": "dashboard_service_links_user_id_users_id_fk", + "tableFrom": "dashboard_service_links", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dashboard_service_links_sync_id_unique": { + "name": "dashboard_service_links_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dismissed_alerts": { + "name": "dismissed_alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file_manager_pinned": { + "name": "file_manager_pinned", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_file_manager_pinned_user": { + "name": "idx_file_manager_pinned_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "file_manager_pinned_user_id_users_id_fk": { + "name": "file_manager_pinned_user_id_users_id_fk", + "tableFrom": "file_manager_pinned", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_pinned_host_id_ssh_data_id_fk": { + "name": "file_manager_pinned_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_pinned", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file_manager_recent": { + "name": "file_manager_recent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_opened": { + "name": "last_opened", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_file_manager_recent_user": { + "name": "idx_file_manager_recent_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "file_manager_recent_user_id_users_id_fk": { + "name": "file_manager_recent_user_id_users_id_fk", + "tableFrom": "file_manager_recent", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_recent_host_id_ssh_data_id_fk": { + "name": "file_manager_recent_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file_manager_shortcuts": { + "name": "file_manager_shortcuts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_file_manager_shortcuts_user": { + "name": "idx_file_manager_shortcuts_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "file_manager_shortcuts_user_id_users_id_fk": { + "name": "file_manager_shortcuts_user_id_users_id_fk", + "tableFrom": "file_manager_shortcuts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_shortcuts_host_id_ssh_data_id_fk": { + "name": "file_manager_shortcuts_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_shortcuts", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder_access": { + "name": "folder_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.homepage_items": { + "name": "homepage_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type_id": { + "name": "type_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_homepage_items_user_id": { + "name": "idx_homepage_items_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "homepage_items_user_id_users_id_fk": { + "name": "homepage_items_user_id_users_id_fk", + "tableFrom": "homepage_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "homepage_items_sync_id_unique": { + "name": "homepage_items_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.homepage_layouts": { + "name": "homepage_layouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "homepage_layouts_user_id_users_id_fk": { + "name": "homepage_layouts_user_id_users_id_fk", + "tableFrom": "homepage_layouts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "homepage_layouts_user_id_unique": { + "name": "homepage_layouts_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_access": { + "name": "host_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_health_checks": { + "name": "host_health_checks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "checks": { + "name": "checks", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_health_checks_user_host": { + "name": "idx_host_health_checks_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_health_checks_user_id_users_id_fk": { + "name": "host_health_checks_user_id_users_id_fk", + "tableFrom": "host_health_checks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_health_checks_host_id_ssh_data_id_fk": { + "name": "host_health_checks_host_id_ssh_data_id_fk", + "tableFrom": "host_health_checks", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_health_history": { + "name": "host_health_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_health_history_user_id_users_id_fk": { + "name": "host_health_history_user_id_users_id_fk", + "tableFrom": "host_health_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_health_history_host_id_ssh_data_id_fk": { + "name": "host_health_history_host_id_ssh_data_id_fk", + "tableFrom": "host_health_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_metrics_history": { + "name": "host_metrics_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "mem_percent": { + "name": "mem_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "net_rx_bytes": { + "name": "net_rx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "net_tx_bytes": { + "name": "net_tx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_metrics_history_host_id_ssh_data_id_fk": { + "name": "host_metrics_history_host_id_ssh_data_id_fk", + "tableFrom": "host_metrics_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_metrics_preferences": { + "name": "host_metrics_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_metrics_prefs_user_host": { + "name": "idx_host_metrics_prefs_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_metrics_preferences_user_id_users_id_fk": { + "name": "host_metrics_preferences_user_id_users_id_fk", + "tableFrom": "host_metrics_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_metrics_preferences_host_id_ssh_data_id_fk": { + "name": "host_metrics_preferences_host_id_ssh_data_id_fk", + "tableFrom": "host_metrics_preferences", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_data": { + "name": "ssh_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "use_warpgate": { + "name": "use_warpgate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autostart_password": { + "name": "autostart_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autostart_key": { + "name": "autostart_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autostart_key_password": { + "name": "autostart_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "vault_profile_id": { + "name": "vault_profile_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enable_terminal": { + "name": "enable_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_session_logging": { + "name": "enable_session_logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_session_sharing": { + "name": "allow_session_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_command_history": { + "name": "enable_command_history", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_tunnel": { + "name": "enable_tunnel", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "tunnel_connections": { + "name": "tunnel_connections", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_file_manager": { + "name": "enable_file_manager", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "scp_legacy": { + "name": "scp_legacy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_docker": { + "name": "enable_docker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_web_ui": { + "name": "enable_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_tmux_monitor": { + "name": "enable_tmux_monitor", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_terminal_toolbar": { + "name": "enable_terminal_toolbar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_ai_assistant": { + "name": "enable_ai_assistant", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_terminal_in_sidebar": { + "name": "show_terminal_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_file_manager_in_sidebar": { + "name": "show_file_manager_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_tunnel_in_sidebar": { + "name": "show_tunnel_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_docker_in_sidebar": { + "name": "show_docker_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_server_stats_in_sidebar": { + "name": "show_server_stats_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_path": { + "name": "default_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stats_config": { + "name": "stats_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "docker_config": { + "name": "docker_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "web_ui_config": { + "name": "web_ui_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_proxmox": { + "name": "enable_proxmox", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "proxmox_config": { + "name": "proxmox_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_proxmox_stats": { + "name": "enable_proxmox_stats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "proxmox_stats_config": { + "name": "proxmox_stats_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_config": { + "name": "terminal_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quick_actions": { + "name": "quick_actions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_rdp": { + "name": "enable_rdp", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_vnc": { + "name": "enable_vnc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_telnet": { + "name": "enable_telnet", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 22 + }, + "rdp_port": { + "name": "rdp_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3389 + }, + "vnc_port": { + "name": "vnc_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5900 + }, + "telnet_port": { + "name": "telnet_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 23 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_security": { + "name": "rdp_security", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_ignore_cert": { + "name": "rdp_ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "security": { + "name": "security", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ignore_cert": { + "name": "ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "guacamole_config": { + "name": "guacamole_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + { + "expression": "parent_host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_channels": { + "name": "notification_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opkssh_tokens": { + "name": "opkssh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_install_counts": { + "name": "plugin_install_counts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_permission_grants": { + "name": "plugin_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "capability", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_registries": { + "name": "plugin_registries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_settings": { + "name": "plugin_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proxmox_node_history": { + "name": "proxmox_node_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "mem_percent": { + "name": "mem_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "net_rx_bytes": { + "name": "net_rx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "net_tx_bytes": { + "name": "net_tx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "proxmox_node_history_host_id_ssh_data_id_fk": { + "name": "proxmox_node_history_host_id_ssh_data_id_fk", + "tableFrom": "proxmox_node_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proxmox_stats_preferences": { + "name": "proxmox_stats_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_proxmox_stats_prefs_user_host": { + "name": "idx_proxmox_stats_prefs_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proxmox_stats_preferences_user_id_users_id_fk": { + "name": "proxmox_stats_preferences_user_id_users_id_fk", + "tableFrom": "proxmox_stats_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proxmox_stats_preferences_host_id_ssh_data_id_fk": { + "name": "proxmox_stats_preferences_host_id_ssh_data_id_fk", + "tableFrom": "proxmox_stats_preferences", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "role_name": { + "name": "role_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + { + "expression": "role_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_known_permissions": { + "name": "rbac_known_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recent_activity": { + "name": "recent_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_sources": { + "name": "secret_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'onepassword-connect'" + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_secret_sources_user": { + "name": "idx_secret_sources_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_sources_user_id_users_id_fk": { + "name": "secret_sources_user_id_users_id_fk", + "tableFrom": "secret_sources", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_recordings": { + "name": "session_recordings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_id": { + "name": "access_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "commands": { + "name": "commands", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dangerous_actions": { + "name": "dangerous_actions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recording_path": { + "name": "recording_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "terminated_by_owner": { + "name": "terminated_by_owner", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "termination_reason": { + "name": "termination_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_session_recordings_user_started": { + "name": "idx_session_recordings_user_started", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_session_recordings_host": { + "name": "idx_session_recordings_host", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_recordings_host_id_ssh_data_id_fk": { + "name": "session_recordings_host_id_ssh_data_id_fk", + "tableFrom": "session_recordings", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_recordings_user_id_users_id_fk": { + "name": "session_recordings_user_id_users_id_fk", + "tableFrom": "session_recordings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "session_recordings_access_id_host_access_id_fk": { + "name": "session_recordings_access_id_host_access_id_fk", + "tableFrom": "session_recordings", + "tableTo": "host_access", + "columnsFrom": [ + "access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_share_participants": { + "name": "session_share_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "share_id": { + "name": "share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "guest_label": { + "name": "guest_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "left_at": { + "name": "left_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_share_participants_share_id_session_shares_id_fk": { + "name": "session_share_participants_share_id_session_shares_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "session_shares", + "columnsFrom": [ + "share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_share_participants_user_id_users_id_fk": { + "name": "session_share_participants_user_id_users_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_shares": { + "name": "session_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_instance_id": { + "name": "tab_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "share_type": { + "name": "share_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "link_token": { + "name": "link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read-only'" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_joined_at": { + "name": "last_joined_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "join_count": { + "name": "join_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "idx_session_shares_session_id": { + "name": "idx_session_shares_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_session_shares_host_id": { + "name": "idx_session_shares_host_id", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_shares_host_id_ssh_data_id_fk": { + "name": "session_shares_host_id_ssh_data_id_fk", + "tableFrom": "session_shares", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_owner_user_id_users_id_fk": { + "name": "session_shares_owner_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_target_user_id_users_id_fk": { + "name": "session_shares_target_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_shares_link_token_unique": { + "name": "session_shares_link_token_unique", + "nullsNotDistinct": false, + "columns": [ + "link_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_credential_secrets": { + "name": "shared_credential_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + { + "expression": "credential_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_secrets": { + "name": "shared_host_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + { + "expression": "host_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credential_usage": { + "name": "ssh_credential_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credentials": { + "name": "ssh_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_folders": { + "name": "ssh_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_providers": { + "name": "sso_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_tombstones": { + "name": "sync_tombstones", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identities": { + "name": "termix_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "nullsNotDistinct": false, + "columns": [ + "handle" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_ca": { + "name": "termix_identity_ca", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "nullsNotDistinct": false, + "columns": [ + "identity_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_keys": { + "name": "termix_identity_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tmux_session_tags": { + "name": "tmux_session_tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_name": { + "name": "session_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "tmux_session_tags_user_id_users_id_fk": { + "name": "tmux_session_tags_user_id_users_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tmux_session_tags_host_id_ssh_data_id_fk": { + "name": "tmux_session_tags_host_id_ssh_data_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transfer_recent": { + "name": "transfer_recent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "source_host_id": { + "name": "source_host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dest_host_id": { + "name": "dest_host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dest_path": { + "name": "dest_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dest_path_label": { + "name": "dest_path_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_transfer_recent_user": { + "name": "idx_transfer_recent_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transfer_recent_user_id_users_id_fk": { + "name": "transfer_recent_user_id_users_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transfer_recent_source_host_id_ssh_data_id_fk": { + "name": "transfer_recent_source_host_id_ssh_data_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "source_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transfer_recent_dest_host_id_ssh_data_id_fk": { + "name": "transfer_recent_dest_host_id_ssh_data_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "dest_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_devices": { + "name": "trusted_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ui_preferences": { + "name": "ui_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_external_identities": { + "name": "user_external_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_open_tabs": { + "name": "user_open_tabs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command_autocomplete": { + "name": "command_autocomplete", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ai_assistant_enabled": { + "name": "ai_assistant_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ai_read_only_commands": { + "name": "ai_read_only_commands", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_defaults": { + "name": "rdp_defaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_second_factors": { + "name": "user_second_factors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "factor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'openid email profile'" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vault_profiles": { + "name": "vault_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vault_tokens": { + "name": "vault_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webauthn_credentials": { + "name": "webauthn_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preferred'" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/postgres/meta/_journal.json b/drizzle/postgres/meta/_journal.json index c43847e40..d87e4038d 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -246,6 +246,13 @@ "when": 1790169398038, "tag": "0034_free_smiling_tiger", "breakpoints": true + }, + { + "idx": 35, + "version": "7", + "when": 1790176258755, + "tag": "0035_ambitious_klaw", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0031_smooth_scarlet_witch.sql b/drizzle/sqlite/0031_smooth_scarlet_witch.sql new file mode 100644 index 000000000..9b782ee9e --- /dev/null +++ b/drizzle/sqlite/0031_smooth_scarlet_witch.sql @@ -0,0 +1,2 @@ +-- dropped, see plugin adoption (fleets plugin, migrations/sqlite/0001_adopt_fleets.sql) +SELECT 1; diff --git a/drizzle/sqlite/meta/0031_snapshot.json b/drizzle/sqlite/meta/0031_snapshot.json new file mode 100644 index 000000000..2344ceff3 --- /dev/null +++ b/drizzle/sqlite/meta/0031_snapshot.json @@ -0,0 +1,8770 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "9b337361-0d36-4e65-a845-6833c0e48066", + "prevId": "5ec852bd-02de-4aab-981e-584cb28003f8", + "tables": { + "ai_conversations": { + "name": "ai_conversations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_conversations_user": { + "name": "idx_ai_conversations_user", + "columns": [ + "user_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_conversations_user_id_users_id_fk": { + "name": "ai_conversations_user_id_users_id_fk", + "tableFrom": "ai_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ai_messages": { + "name": "ai_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_messages_conversation": { + "name": "idx_ai_messages_conversation", + "columns": [ + "conversation_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_messages_conversation_id_ai_conversations_id_fk": { + "name": "ai_messages_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_messages", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ai_proposals": { + "name": "ai_proposals", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_proposals_user": { + "name": "idx_ai_proposals_user", + "columns": [ + "user_id", + "status" + ], + "isUnique": false + }, + "idx_ai_proposals_conversation": { + "name": "idx_ai_proposals_conversation", + "columns": [ + "conversation_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_proposals_conversation_id_ai_conversations_id_fk": { + "name": "ai_proposals_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_proposals_user_id_users_id_fk": { + "name": "ai_proposals_user_id_users_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ai_providers": { + "name": "ai_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_key": { + "name": "api_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_key_prefix": { + "name": "api_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_providers_user_label": { + "name": "idx_ai_providers_user_label", + "columns": [ + "user_id", + "label" + ], + "isUnique": true + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_channels": { + "name": "automation_channels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_channels_pair": { + "name": "idx_automation_channels_pair", + "columns": [ + "automation_id", + "channel_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_channels_automation_id_automations_id_fk": { + "name": "automation_channels_automation_id_automations_id_fk", + "tableFrom": "automation_channels", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_channels_channel_id_notification_channels_id_fk": { + "name": "automation_channels_channel_id_notification_channels_id_fk", + "tableFrom": "automation_channels", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_run_steps": { + "name": "automation_run_steps", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_index": { + "name": "step_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_type": { + "name": "step_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "truncated": { + "name": "truncated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_automation_run_steps_run": { + "name": "idx_automation_run_steps_run", + "columns": [ + "run_id", + "step_index" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_run_steps_run_id_automation_runs_id_fk": { + "name": "automation_run_steps_run_id_automation_runs_id_fk", + "tableFrom": "automation_run_steps", + "tableTo": "automation_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_runs": { + "name": "automation_runs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_context": { + "name": "trigger_context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dry_run": { + "name": "dry_run", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_runs_automation": { + "name": "idx_automation_runs_automation", + "columns": [ + "automation_id", + "started_at" + ], + "isUnique": false + }, + "idx_automation_runs_user": { + "name": "idx_automation_runs_user", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_user_id_users_id_fk": { + "name": "automation_runs_user_id_users_id_fk", + "tableFrom": "automation_runs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_schedules": { + "name": "automation_schedules", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_due_at": { + "name": "next_due_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_tick_at": { + "name": "last_tick_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_schedules_automation": { + "name": "idx_automation_schedules_automation", + "columns": [ + "automation_id" + ], + "isUnique": true + }, + "idx_automation_schedules_due": { + "name": "idx_automation_schedules_due", + "columns": [ + "next_due_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_schedules_automation_id_automations_id_fk": { + "name": "automation_schedules_automation_id_automations_id_fk", + "tableFrom": "automation_schedules", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_trigger_state": { + "name": "automation_trigger_state", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "breach_started_at": { + "name": "breach_started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_value": { + "name": "last_value", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_observed_state": { + "name": "last_observed_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automation_trigger_state_key": { + "name": "idx_automation_trigger_state_key", + "columns": [ + "automation_id", + "state_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_trigger_state_automation_id_automations_id_fk": { + "name": "automation_trigger_state_automation_id_automations_id_fk", + "tableFrom": "automation_trigger_state", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "definition_version": { + "name": "definition_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'skip'" + }, + "max_run_seconds": { + "name": "max_run_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "dry_run": { + "name": "dry_run", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automations_user": { + "name": "idx_automations_user", + "columns": [ + "user_id", + "enabled" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_user_id_users_id_fk": { + "name": "automations_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "c2s_tunnel_presets": { + "name": "c2s_tunnel_presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "computer_name": { + "name": "computer_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "c2s_tunnel_presets_user_id_users_id_fk": { + "name": "c2s_tunnel_presets_user_id_users_id_fk", + "tableFrom": "c2s_tunnel_presets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "collab_room_members": { + "name": "collab_room_members", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_role": { + "name": "room_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_collab_room_members_room_user": { + "name": "idx_collab_room_members_room_user", + "columns": [ + "room_id", + "user_id" + ], + "isUnique": true + }, + "idx_collab_room_members_user": { + "name": "idx_collab_room_members_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "collab_room_members_room_id_collab_rooms_id_fk": { + "name": "collab_room_members_room_id_collab_rooms_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "collab_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_user_id_users_id_fk": { + "name": "collab_room_members_user_id_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_added_by_users_id_fk": { + "name": "collab_room_members_added_by_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "collab_rooms": { + "name": "collab_rooms", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "persistent": { + "name": "persistent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "presenter_user_id": { + "name": "presenter_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_protocol": { + "name": "stage_protocol", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_host_id": { + "name": "stage_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_share_id": { + "name": "stage_share_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_link_token": { + "name": "guest_link_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_collab_rooms_owner": { + "name": "idx_collab_rooms_owner", + "columns": [ + "owner_user_id" + ], + "isUnique": false + }, + "idx_collab_rooms_guest_token": { + "name": "idx_collab_rooms_guest_token", + "columns": [ + "guest_link_token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "collab_rooms_owner_user_id_users_id_fk": { + "name": "collab_rooms_owner_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_rooms_presenter_user_id_users_id_fk": { + "name": "collab_rooms_presenter_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "presenter_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_host_id_ssh_data_id_fk": { + "name": "collab_rooms_stage_host_id_ssh_data_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "ssh_data", + "columnsFrom": [ + "stage_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_share_id_session_shares_id_fk": { + "name": "collab_rooms_stage_share_id_session_shares_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "session_shares", + "columnsFrom": [ + "stage_share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "command_history": { + "name": "command_history", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "executed_at": { + "name": "executed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_command_history_user_host": { + "name": "idx_command_history_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "command_history_user_id_users_id_fk": { + "name": "command_history_user_id_users_id_fk", + "tableFrom": "command_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "command_history_host_id_ssh_data_id_fk": { + "name": "command_history_host_id_ssh_data_id_fk", + "tableFrom": "command_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "dashboard_service_links": { + "name": "dashboard_service_links", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "dashboard_service_links_sync_id_unique": { + "name": "dashboard_service_links_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "dashboard_service_links_user_id_users_id_fk": { + "name": "dashboard_service_links_user_id_users_id_fk", + "tableFrom": "dashboard_service_links", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "dismissed_alerts": { + "name": "dismissed_alerts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "file_manager_pinned": { + "name": "file_manager_pinned", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_file_manager_pinned_user": { + "name": "idx_file_manager_pinned_user", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "file_manager_pinned_user_id_users_id_fk": { + "name": "file_manager_pinned_user_id_users_id_fk", + "tableFrom": "file_manager_pinned", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_pinned_host_id_ssh_data_id_fk": { + "name": "file_manager_pinned_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_pinned", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "file_manager_recent": { + "name": "file_manager_recent", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened": { + "name": "last_opened", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_file_manager_recent_user": { + "name": "idx_file_manager_recent_user", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "file_manager_recent_user_id_users_id_fk": { + "name": "file_manager_recent_user_id_users_id_fk", + "tableFrom": "file_manager_recent", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_recent_host_id_ssh_data_id_fk": { + "name": "file_manager_recent_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "file_manager_shortcuts": { + "name": "file_manager_shortcuts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_file_manager_shortcuts_user": { + "name": "idx_file_manager_shortcuts_user", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "file_manager_shortcuts_user_id_users_id_fk": { + "name": "file_manager_shortcuts_user_id_users_id_fk", + "tableFrom": "file_manager_shortcuts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_manager_shortcuts_host_id_ssh_data_id_fk": { + "name": "file_manager_shortcuts_host_id_ssh_data_id_fk", + "tableFrom": "file_manager_shortcuts", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "folder_access": { + "name": "folder_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + "owner_user_id", + "folder" + ], + "isUnique": false + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "homepage_items": { + "name": "homepage_items", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type_id": { + "name": "type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "homepage_items_sync_id_unique": { + "name": "homepage_items_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_homepage_items_user_id": { + "name": "idx_homepage_items_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "homepage_items_user_id_users_id_fk": { + "name": "homepage_items_user_id_users_id_fk", + "tableFrom": "homepage_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "homepage_layouts": { + "name": "homepage_layouts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "homepage_layouts_user_id_unique": { + "name": "homepage_layouts_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "homepage_layouts_user_id_users_id_fk": { + "name": "homepage_layouts_user_id_users_id_fk", + "tableFrom": "homepage_layouts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_access": { + "name": "host_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_health_checks": { + "name": "host_health_checks", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checks": { + "name": "checks", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_health_checks_user_host": { + "name": "idx_host_health_checks_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "host_health_checks_user_id_users_id_fk": { + "name": "host_health_checks_user_id_users_id_fk", + "tableFrom": "host_health_checks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_health_checks_host_id_ssh_data_id_fk": { + "name": "host_health_checks_host_id_ssh_data_id_fk", + "tableFrom": "host_health_checks", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_health_history": { + "name": "host_health_history", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "ok": { + "name": "ok", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_health_history_user_id_users_id_fk": { + "name": "host_health_history_user_id_users_id_fk", + "tableFrom": "host_health_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_health_history_host_id_ssh_data_id_fk": { + "name": "host_health_history_host_id_ssh_data_id_fk", + "tableFrom": "host_health_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_metrics_history": { + "name": "host_metrics_history", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mem_percent": { + "name": "mem_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_rx_bytes": { + "name": "net_rx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_tx_bytes": { + "name": "net_tx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_metrics_history_host_id_ssh_data_id_fk": { + "name": "host_metrics_history_host_id_ssh_data_id_fk", + "tableFrom": "host_metrics_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_metrics_preferences": { + "name": "host_metrics_preferences", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_metrics_prefs_user_host": { + "name": "idx_host_metrics_prefs_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "host_metrics_preferences_user_id_users_id_fk": { + "name": "host_metrics_preferences_user_id_users_id_fk", + "tableFrom": "host_metrics_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_metrics_preferences_host_id_ssh_data_id_fk": { + "name": "host_metrics_preferences_host_id_ssh_data_id_fk", + "tableFrom": "host_metrics_preferences", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_data": { + "name": "ssh_data", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_warpgate": { + "name": "use_warpgate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_password": { + "name": "autostart_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key": { + "name": "autostart_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key_password": { + "name": "autostart_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_profile_id": { + "name": "vault_profile_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_terminal": { + "name": "enable_terminal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_session_logging": { + "name": "enable_session_logging", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_session_sharing": { + "name": "allow_session_sharing", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_command_history": { + "name": "enable_command_history", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_tunnel": { + "name": "enable_tunnel", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "tunnel_connections": { + "name": "tunnel_connections", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_file_manager": { + "name": "enable_file_manager", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "scp_legacy": { + "name": "scp_legacy", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_docker": { + "name": "enable_docker", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_web_ui": { + "name": "enable_web_ui", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_tmux_monitor": { + "name": "enable_tmux_monitor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_terminal_toolbar": { + "name": "enable_terminal_toolbar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_ai_assistant": { + "name": "enable_ai_assistant", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_terminal_in_sidebar": { + "name": "show_terminal_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_file_manager_in_sidebar": { + "name": "show_file_manager_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_tunnel_in_sidebar": { + "name": "show_tunnel_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_docker_in_sidebar": { + "name": "show_docker_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_server_stats_in_sidebar": { + "name": "show_server_stats_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_path": { + "name": "default_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stats_config": { + "name": "stats_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "docker_config": { + "name": "docker_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "web_ui_config": { + "name": "web_ui_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_proxmox": { + "name": "enable_proxmox", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "proxmox_config": { + "name": "proxmox_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_proxmox_stats": { + "name": "enable_proxmox_stats", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "proxmox_stats_config": { + "name": "proxmox_stats_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_config": { + "name": "terminal_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quick_actions": { + "name": "quick_actions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_rdp": { + "name": "enable_rdp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_vnc": { + "name": "enable_vnc", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_telnet": { + "name": "enable_telnet", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "rdp_port": { + "name": "rdp_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 3389 + }, + "vnc_port": { + "name": "vnc_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 5900 + }, + "telnet_port": { + "name": "telnet_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 23 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_security": { + "name": "rdp_security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_ignore_cert": { + "name": "rdp_ignore_cert", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "security": { + "name": "security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ignore_cert": { + "name": "ignore_cert", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "guacamole_config": { + "name": "guacamole_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_socks5": { + "name": "use_socks5", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "opkssh_tokens": { + "name": "opkssh_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "proxmox_node_history": { + "name": "proxmox_node_history", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ts": { + "name": "ts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mem_percent": { + "name": "mem_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_rx_bytes": { + "name": "net_rx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "net_tx_bytes": { + "name": "net_tx_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "proxmox_node_history_host_id_ssh_data_id_fk": { + "name": "proxmox_node_history_host_id_ssh_data_id_fk", + "tableFrom": "proxmox_node_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "proxmox_stats_preferences": { + "name": "proxmox_stats_preferences", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_proxmox_stats_prefs_user_host": { + "name": "idx_proxmox_stats_prefs_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "proxmox_stats_preferences_user_id_users_id_fk": { + "name": "proxmox_stats_preferences_user_id_users_id_fk", + "tableFrom": "proxmox_stats_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proxmox_stats_preferences_host_id_ssh_data_id_fk": { + "name": "proxmox_stats_preferences_host_id_ssh_data_id_fk", + "tableFrom": "proxmox_stats_preferences", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "role_name": { + "name": "role_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + "role_name", + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rbac_known_permissions": { + "name": "rbac_known_permissions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recent_activity": { + "name": "recent_activity", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "roles": { + "name": "roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "roles_name_unique": { + "name": "roles_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "secret_sources": { + "name": "secret_sources", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'onepassword-connect'" + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_secret_sources_user": { + "name": "idx_secret_sources_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "secret_sources_user_id_users_id_fk": { + "name": "secret_sources_user_id_users_id_fk", + "tableFrom": "secret_sources", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_recordings": { + "name": "session_recordings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_id": { + "name": "access_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commands": { + "name": "commands", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dangerous_actions": { + "name": "dangerous_actions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recording_path": { + "name": "recording_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "terminated_by_owner": { + "name": "terminated_by_owner", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "termination_reason": { + "name": "termination_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_session_recordings_user_started": { + "name": "idx_session_recordings_user_started", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + }, + "idx_session_recordings_host": { + "name": "idx_session_recordings_host", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_recordings_host_id_ssh_data_id_fk": { + "name": "session_recordings_host_id_ssh_data_id_fk", + "tableFrom": "session_recordings", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_recordings_user_id_users_id_fk": { + "name": "session_recordings_user_id_users_id_fk", + "tableFrom": "session_recordings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "session_recordings_access_id_host_access_id_fk": { + "name": "session_recordings_access_id_host_access_id_fk", + "tableFrom": "session_recordings", + "tableTo": "host_access", + "columnsFrom": [ + "access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_share_participants": { + "name": "session_share_participants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "share_id": { + "name": "share_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_label": { + "name": "guest_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "left_at": { + "name": "left_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_share_participants_share_id_session_shares_id_fk": { + "name": "session_share_participants_share_id_session_shares_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "session_shares", + "columnsFrom": [ + "share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_share_participants_user_id_users_id_fk": { + "name": "session_share_participants_user_id_users_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_shares": { + "name": "session_shares", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_instance_id": { + "name": "tab_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_type": { + "name": "share_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "link_token": { + "name": "link_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'read-only'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_joined_at": { + "name": "last_joined_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "join_count": { + "name": "join_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "session_shares_link_token_unique": { + "name": "session_shares_link_token_unique", + "columns": [ + "link_token" + ], + "isUnique": true + }, + "idx_session_shares_session_id": { + "name": "idx_session_shares_session_id", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_session_shares_host_id": { + "name": "idx_session_shares_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_shares_host_id_ssh_data_id_fk": { + "name": "session_shares_host_id_ssh_data_id_fk", + "tableFrom": "session_shares", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_owner_user_id_users_id_fk": { + "name": "session_shares_owner_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_target_user_id_users_id_fk": { + "name": "session_shares_target_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "target_user_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 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sso_providers": { + "name": "sso_providers", + "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 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identities": { + "name": "termix_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tmux_session_tags": { + "name": "tmux_session_tags", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_name": { + "name": "session_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "tmux_session_tags_user_id_users_id_fk": { + "name": "tmux_session_tags_user_id_users_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tmux_session_tags_host_id_ssh_data_id_fk": { + "name": "tmux_session_tags_host_id_ssh_data_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "transfer_recent": { + "name": "transfer_recent", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_host_id": { + "name": "source_host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dest_host_id": { + "name": "dest_host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dest_path": { + "name": "dest_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dest_path_label": { + "name": "dest_path_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_transfer_recent_user": { + "name": "idx_transfer_recent_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "transfer_recent_user_id_users_id_fk": { + "name": "transfer_recent_user_id_users_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transfer_recent_source_host_id_ssh_data_id_fk": { + "name": "transfer_recent_source_host_id_ssh_data_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "source_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transfer_recent_dest_host_id_ssh_data_id_fk": { + "name": "transfer_recent_dest_host_id_ssh_data_id_fk", + "tableFrom": "transfer_recent", + "tableTo": "ssh_data", + "columnsFrom": [ + "dest_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_autocomplete": { + "name": "command_autocomplete", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_assistant_enabled": { + "name": "ai_assistant_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_read_only_commands": { + "name": "ai_read_only_commands", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_defaults": { + "name": "rdp_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'openid email profile'" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vault_profiles": { + "name": "vault_profiles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vault_tokens": { + "name": "vault_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + "user_id", + "profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webauthn_credentials": { + "name": "webauthn_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backed_up": { + "name": "backed_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'preferred'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/sqlite/meta/_journal.json b/drizzle/sqlite/meta/_journal.json index 440ea5b57..e1d0f60d2 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -218,6 +218,13 @@ "when": 1790169397528, "tag": "0030_quick_felicia_hardy", "breakpoints": true + }, + { + "idx": 31, + "version": "6", + "when": 1790176258239, + "tag": "0031_smooth_scarlet_witch", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/plugin-sdk/ARCHITECTURE.md b/packages/plugin-sdk/ARCHITECTURE.md index fface6007..1ef7be028 100644 --- a/packages/plugin-sdk/ARCHITECTURE.md +++ b/packages/plugin-sdk/ARCHITECTURE.md @@ -26,18 +26,19 @@ This is not a sandbox. See [What this protects](#what-this-protects-and-what-it- A plugin imports `@termix/plugin-sdk`, its own files and its own npm dependencies. Entry points: -| Entry | Contents | -| ---------------------------------- | ---------------------------------------------------------- | -| `@termix/plugin-sdk/backend` | `PluginContext`, `definePlugin()`, `PluginCapabilityError` | -| `@termix/plugin-sdk/db` | `defineTable()`, the column builders, the legacy-table map | -| `@termix/plugin-sdk/ddl` | The per-dialect DDL emitter, shared with the CLI | -| `@termix/plugin-sdk/table-builder` | `buildTable()`, a definition as a queryable Drizzle table | -| `@termix/plugin-sdk/frontend` | The `app` object types, the hooks, `invokeAction()` | -| `@termix/plugin-sdk/ui` | Core's shared components, a fixed public list (see UI) | -| `@termix/plugin-sdk/manifest` | Manifest types, validation, the JSON schema | -| `@termix/plugin-sdk/capabilities` | The capability catalog | -| `@termix/plugin-sdk/settings` | Settings field types and the shared value validator | -| `@termix/plugin-sdk/testing` | Test helpers: `createMockCtx()`, `createTestDb()` and more | +| Entry | Contents | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `@termix/plugin-sdk/backend` | `PluginContext`, `definePlugin()`, `PluginCapabilityError` | +| `@termix/plugin-sdk/db` | `defineTable()`, the column builders, the legacy-table map | +| `@termix/plugin-sdk/ddl` | The per-dialect DDL emitter, shared with the CLI | +| `@termix/plugin-sdk/table-builder` | `buildTable()`, a definition as a queryable Drizzle table | +| `@termix/plugin-sdk/frontend` | The `app` object types, the hooks, `invokeAction()` | +| `@termix/plugin-sdk/ui` | Core's shared components, a fixed public list (see UI) | +| `@termix/plugin-sdk/manifest` | Manifest types, validation, the JSON schema | +| `@termix/plugin-sdk/capabilities` | The capability catalog | +| `@termix/plugin-sdk/settings` | Settings field types and the shared value validator | +| `@termix/plugin-sdk/host-commands` | Platform detection, package manager commands, sudo elevation - pure helpers over an ssh2 `Client` a plugin got from `ctx.ssh` | +| `@termix/plugin-sdk/testing` | Test helpers: `createMockCtx()`, `createTestDb()` and more | Plugins never import from `src/` or `@/`, and core never imports from `plugins/`. Anything a plugin needs from core becomes a typed, documented SDK @@ -1045,7 +1046,7 @@ Built per plugin in `src/backend/plugins/ctx.ts` and passed to `activate`. | `ctx.sync.registerEntity` | none | **A3** | | `ctx.http.router` / `ctx.ws.route` / `.upgrade` | `network:serve` | **A4** | | `ctx.rbac.has` / `.hasFor` / `.require` | own permissions only | **A5** | -| `ctx.hosts.*` | `hosts:read` / `hosts:write` | B | +| `ctx.hosts.*` | `hosts:read` / `hosts:write` | **B4** | | `ctx.ssh.*` | `ssh:connect`, `credentials:use` | **A8** | | `ctx.settings.*` | `settings:read-core` (readCore only) | **A6** | | `ctx.notify.*` | `notify:send` | A6 | @@ -1073,6 +1074,32 @@ connect pipeline instead of importing ssh2 helpers from core: Each new connection is audited and runs as the current actor; pooled reuse is not. Connections and pool entries are closed on deactivate. +**B4** added `ctx.hosts`, for a plugin that groups or acts across hosts it +does not own the way fleets does: + +- `list()` / `get(hostId)` return `PluginHostSummary` (id, userId, name, ip, + port, username, tags, folder, authType - no secrets) for the acting user's + own and shared hosts. Needs `hosts:read`. +- `checkAccess(hostId, level)` mirrors `PermissionManager.canAccessHost`, + naming the level ("connect" | "view" | "edit" | "manage") rather than a + bare yes/no, because a plugin fanning out across a fleet needs to know + whether it can only connect, or also manage. Needs `hosts:read`. +- `share(hostId, targets, permissionLevel, durationHours)` grants access the + same way the host editor's own share action does, snapshotting shared + secrets per target through core's `SharedHostSecretsManager`. Refuses a + host the caller does not hold "manage" on. Needs `hosts:write`, because + granting access to a host is a write on that host even when the plugin + owns neither the host nor the grant. +- `listUsers()` / `listRoles()` return minimal `{ id, username }` / + `{ id, name, displayName }` lists (roles filtered to non-system) for a + share-target picker UI, without exposing the full admin user/role + management surface. Needs `hosts:write`, matching `share`. + +`ctx.hosts.get` returns the same non-secret projection as `list`; a plugin +that needs to actually connect uses `ctx.ssh.connect(hostId)` or +`withConnection(hostId, ...)`, which resolve the full host (secrets included) +through the one connect pipeline. fleets never holds a `PluginSshHost` itself. + ### The actor Never comes from plugin code. It is held in `AsyncLocalStorage` and set two diff --git a/packages/plugin-sdk/FINISH-LIST.md b/packages/plugin-sdk/FINISH-LIST.md index b4b48ad37..a734aac80 100644 --- a/packages/plugin-sdk/FINISH-LIST.md +++ b/packages/plugin-sdk/FINISH-LIST.md @@ -13,7 +13,13 @@ it's done. - **B3 (tailscale):** `plugins/tailscale/src/backend/host-metrics-manager.ts` and `routes.ts` still resolve hosts and access through core's `PermissionManager`/`resolveHostById`/`DataCrypto` by relative import - instead of a `ctx.hosts`-style SDK API, because `ctx.hosts` is not built yet - (status **B** in the ctx surface table). Same debt every other host-metrics - manager already carries; not new, but tailscale now carries it too. Owner: - whichever step builds `ctx.hosts`. + instead of `ctx.hosts`, which **B4** built. Same debt every other + host-metrics manager already carries; not new, but tailscale now carries it + too. Owner: host-metrics's own Phase B step (it and every manager plugin + convert together). +- **B4 (fleets/host-metrics):** `src/backend/hosts/metrics-shared/` (platform + detection, package commands, exec-elevated) is still core's own copy, used + by host-metrics, which has not converted yet. `@termix/plugin-sdk/host-commands` + is the SDK copy fleets now uses; core's copy was left in place rather than + turned into a re-export, since host-metrics's own conversion step is where + its imports should move wholesale. Owner: host-metrics's Phase B step. diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index 40cfaff51..cdc1a7303 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -52,6 +52,10 @@ "types": "./dist/vitest-preset.d.ts", "default": "./dist/vitest-preset.js" }, + "./host-commands": { + "types": "./dist/host-commands.d.ts", + "default": "./dist/host-commands.js" + }, "./tsconfig.plugin.json": "./tsconfig.plugin.json", "./schema/manifest.schema.json": "./schema/manifest.schema.json" }, @@ -71,11 +75,15 @@ "tar": "^7.5.22" }, "peerDependencies": { - "drizzle-orm": "*" + "drizzle-orm": "*", + "ssh2": "*" }, "peerDependenciesMeta": { "drizzle-orm": { "optional": true + }, + "ssh2": { + "optional": true } } } diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index 5c64976d8..84cd108e8 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -395,6 +395,90 @@ export interface PluginSettings { readCore: (key: string) => Promise; } +/** A host's non-secret fields, for listing, tag matching and display. */ +export interface PluginHostSummary { + id: number; + userId: string; + name: string | null; + ip: string; + port: number; + username: string; + /** Comma-separated, as ssh_data stores them. Split on "," to match tags. */ + tags: string | null; + folder: string | null; + authType: string; +} + +/** The same shape canAccessHost returns internally, without secrets. */ +export interface PluginHostAccess { + hasAccess: boolean; + isOwner: boolean; + isShared: boolean; + permissionLevel?: "connect" | "view" | "edit" | "manage"; + expiresAt?: string | null; +} + +export type PluginHostShareLevel = "connect" | "view" | "edit" | "manage"; + +export interface PluginShareTarget { + type: "user" | "role"; + id: string | number; +} + +export interface PluginHostShareResult { + hostId: number; + shared: boolean; + reason?: string; +} + +/** A user or role a plugin can offer as a share target. No secrets, no roles' permission lists. */ +export interface PluginShareableUser { + id: string; + username: string; +} + +export interface PluginShareableRole { + id: number; + name: string; + displayName: string | null; +} + +/** + * Hosts the acting user can see, and the RBAC + sharing operations a plugin + * needs to build a feature (like fleets) on top of hosts it does not own. + * + * list/get/checkAccess need hosts:read. share, and the user/role pickers a + * share UI needs, need hosts:write: sharing changes who can reach a host, + * which is a write on that host's access, even though the plugin owns + * neither the host nor the grant. + */ +export interface PluginHosts { + /** Every host the acting user owns or was shared, without secrets. */ + list: () => Promise; + /** One host the acting user can see, or null if it does not exist or they cannot. */ + get: (hostId: number) => Promise; + /** The same access rule ctx.ssh.connect enforces, but naming the level, not just yes/no. */ + checkAccess: ( + hostId: number, + level: PluginHostShareLevel, + ) => Promise; + /** + * Grants access to a host the caller manages, snapshotting shared secrets + * for each target the same way the host editor's own share action does. + * Refuses a host the caller does not hold "manage" on. + */ + share: ( + hostId: number, + targets: PluginShareTarget[], + permissionLevel: PluginHostShareLevel, + durationHours?: number, + ) => Promise; + /** Users the caller may pick as a share target, for building a picker UI. */ + listUsers: () => Promise; + /** Non-system roles the caller may pick as a share target. */ + listRoles: () => Promise; +} + /** * A host as the SSH pipeline reads it: resolved by core, with secrets filled * in for the acting user. Plugins get one from core and hand it back; they @@ -814,6 +898,8 @@ export interface PluginContext { readonly rbac: PluginRbac; readonly settings: PluginSettings; readonly disposables: PluginDisposables; + /** Hosts the actor can see, and host-sharing operations. Needs hosts:read / hosts:write. */ + readonly hosts: PluginHosts; /** SSH through core's connect pipeline. Needs ssh:connect and credentials:use. */ readonly ssh: PluginSsh; /** Login methods, second factors and SSH auth types. Needs auth:provide. */ diff --git a/packages/plugin-sdk/src/host-commands.ts b/packages/plugin-sdk/src/host-commands.ts new file mode 100644 index 000000000..23df504dd --- /dev/null +++ b/packages/plugin-sdk/src/host-commands.ts @@ -0,0 +1,498 @@ +/** + * Generic host command helpers: platform detection, package manager actions + * and the shell-argument validators that guard them. + * + * Pure functions and thin ssh2 wrappers, no core imports. fleets and + * host-metrics both build their platform/package features on this instead of + * duplicating it or reaching into core. + * + * A plugin passes the `Client` it got from ctx.ssh.connect/withConnection. + */ + +export type { Client, ClientChannel } from "ssh2"; +import type { Client, ClientChannel } from "ssh2"; + +export interface ExecResult { + stdout: string; + stderr: string; + code: number | null; +} + +/** Runs one non-interactive command over an existing ssh2 client. */ +export function execCommand( + client: Client, + command: string, + timeoutMs = 30000, +): Promise { + return new Promise((resolve, reject) => { + let settled = false; + let stream: ClientChannel | null = null; + + const timeout = setTimeout(() => { + if (!settled) { + settled = true; + cleanup(); + reject(new Error(`Command timeout after ${timeoutMs}ms: ${command}`)); + } + }, timeoutMs); + + const cleanup = () => { + clearTimeout(timeout); + if (stream) { + try { + stream.removeAllListeners(); + if (stream.stderr) { + stream.stderr.removeAllListeners(); + } + stream.destroy(); + } catch { + // expected - cleanup errors ignored + } + } + }; + + client.exec(command, { pty: false }, (err, _stream) => { + if (err) { + if (!settled) { + settled = true; + cleanup(); + reject(err); + } + return; + } + + stream = _stream; + let stdout = ""; + let stderr = ""; + let exitCode: number | null = null; + + stream + .on("close", (code: number | undefined) => { + if (!settled) { + settled = true; + exitCode = typeof code === "number" ? code : null; + cleanup(); + resolve({ stdout, stderr, code: exitCode }); + } + }) + .on("data", (data: Buffer) => { + stdout += data.toString("utf8"); + }) + .on("error", (streamErr: Error) => { + if (!settled) { + settled = true; + cleanup(); + reject(streamErr); + } + }); + + if (stream.stderr) { + stream.stderr + .on("data", (data: Buffer) => { + stderr += data.toString("utf8"); + }) + .on("error", (stderrErr: Error) => { + if (!settled) { + settled = true; + cleanup(); + reject(stderrErr); + } + }); + } + }); + }); +} + +export type PackageManager = "apt" | "dnf" | "yum" | "pacman" | null; + +export interface PlatformInfo { + hasSystemd: boolean; + pkg: PackageManager; + hasCertbot: boolean; + hasAcmeSh: boolean; + hasDocker: boolean; + osPrettyName: string | null; +} + +/** + * Non-interactive SSH shells don't source ~/.zprofile or ~/.bash_profile, + * so PATH additions from installers like Homebrew or OrbStack are missing. + * Extend PATH with their common install locations before probing. + */ +export const EXTRA_PATH_DIRS = + '/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:"$HOME/.orbstack/bin"'; + +const DOCKER_PROBE = + `command -v docker >/dev/null 2>&1` + + ` || [ -S /var/run/docker.sock ]` + + ` || [ -S "$HOME/.orbstack/run/docker.sock" ]`; + +/** + * Single probe that reports which tooling is available. Each line is + * "key=value" so the parser is trivial and order-independent. + */ +export const PLATFORM_PROBE_COMMAND = [ + `PATH="${EXTRA_PATH_DIRS}:$PATH"`, + "echo systemd=$(command -v systemctl >/dev/null 2>&1 && echo 1 || echo 0)", + "echo apt=$(command -v apt-get >/dev/null 2>&1 && echo 1 || echo 0)", + "echo dnf=$(command -v dnf >/dev/null 2>&1 && echo 1 || echo 0)", + "echo yum=$(command -v yum >/dev/null 2>&1 && echo 1 || echo 0)", + "echo pacman=$(command -v pacman >/dev/null 2>&1 && echo 1 || echo 0)", + "echo certbot=$(command -v certbot >/dev/null 2>&1 && echo 1 || echo 0)", + 'echo acmesh=$( { command -v acme.sh >/dev/null 2>&1 || [ -x "$HOME/.acme.sh/acme.sh" ]; } && echo 1 || echo 0)', + `echo docker=$(${DOCKER_PROBE} && echo 1 || echo 0)`, + 'echo os=$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME")', +].join("; "); + +export function parsePlatformProbe(output: string): PlatformInfo { + const map = new Map(); + for (const line of output.split("\n")) { + const idx = line.indexOf("="); + if (idx === -1) continue; + map.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim()); + } + const on = (k: string) => map.get(k) === "1"; + + // Prefer dnf over yum when both exist (dnf is the modern front-end). + let pkg: PackageManager = null; + if (on("apt")) pkg = "apt"; + else if (on("dnf")) pkg = "dnf"; + else if (on("yum")) pkg = "yum"; + else if (on("pacman")) pkg = "pacman"; + + const os = map.get("os"); + return { + hasSystemd: on("systemd"), + pkg, + hasCertbot: on("certbot"), + hasAcmeSh: on("acmesh"), + hasDocker: on("docker"), + osPrettyName: os && os.length > 0 ? os : null, + }; +} + +export async function detectPlatform(client: Client): Promise { + const { stdout } = await execCommand(client, PLATFORM_PROBE_COMMAND, 15000); + return parsePlatformProbe(stdout); +} + +export type HostPlatform = "darwin" | "linux" | "windows" | "other"; + +// Windows OpenSSH's default shell is usually cmd.exe (occasionally +// PowerShell), so scripts are sent base64-encoded via -EncodedCommand - +// that avoids every cmd.exe quoting/escaping pitfall entirely, since the +// argument ends up being plain alphanumeric text with no shell metacharacters. +export function encodePowerShellCommand(script: string): string { + return Buffer.from(script, "utf16le").toString("base64"); +} + +export function execPowerShell( + client: Client, + script: string, + timeoutMs = 30000, +): Promise { + const encoded = encodePowerShellCommand(script); + return execCommand( + client, + `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`, + timeoutMs, + ); +} + +export async function detectHostPlatform( + client: Client, +): Promise { + try { + const { stdout, code } = await execCommand(client, "uname -s", 10000); + const kernel = stdout.trim().toLowerCase(); + if (kernel === "darwin") return "darwin"; + if (kernel === "linux") return "linux"; + if (code === 0 && kernel) return "other"; + } catch { + // expected on hosts with no POSIX shell (e.g. Windows via cmd.exe) + } + + try { + const { stdout } = await execPowerShell(client, "'WIN_OK'", 10000); + if (stdout.includes("WIN_OK")) return "windows"; + } catch { + // expected on hosts with no PowerShell available + } + + return "other"; +} + +export interface UpgradablePackage { + name: string; + currentVersion?: string; + newVersion?: string; +} + +export function buildListUpgradableCommand(pkg: PackageManager): string | null { + switch (pkg) { + case "apt": + return "apt list --upgradable 2>/dev/null | tail -n +2"; + case "dnf": + return "dnf -q check-update 2>/dev/null || true"; + case "yum": + return "yum -q check-update 2>/dev/null || true"; + case "pacman": + return "pacman -Qu 2>/dev/null || true"; + default: + return null; + } +} + +export function parseUpgradable( + pkg: PackageManager, + output: string, +): UpgradablePackage[] { + const out: UpgradablePackage[] = []; + const lines = output + .split("\n") + .map((l) => l.trim()) + .filter(Boolean); + if (pkg === "apt") { + for (const line of lines) { + // name/suite newver arch [upgradable from: oldver] + const m = line.match( + /^([^/\s]+)\/\S+\s+(\S+)\s+\S+(?:\s+\[upgradable from:\s+(\S+)\])?/, + ); + if (m) out.push({ name: m[1], newVersion: m[2], currentVersion: m[3] }); + } + } else if (pkg === "dnf" || pkg === "yum") { + for (const line of lines) { + if (/^(Last metadata|Obsoleting|Security:)/i.test(line)) continue; + const m = line.match(/^(\S+)\s+(\S+)\s+\S+$/); + if (m && m[1].includes(".")) out.push({ name: m[1], newVersion: m[2] }); + } + } else if (pkg === "pacman") { + for (const line of lines) { + const m = line.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)$/); + if (m) out.push({ name: m[1], currentVersion: m[2], newVersion: m[3] }); + } + } + return out; +} + +export type PackageAction = "upgrade-all" | "install" | "upgrade"; + +export function buildPackageActionCommand( + pkg: PackageManager, + action: PackageAction, + name?: string, +): string | null { + const target = name ? ` ${name}` : ""; + switch (pkg) { + case "apt": + if (action === "upgrade-all") + return "DEBIAN_FRONTEND=noninteractive apt-get -y upgrade"; + return `DEBIAN_FRONTEND=noninteractive apt-get -y install${target}`; + case "dnf": + return action === "upgrade-all" + ? "dnf -y upgrade" + : `dnf -y install${target}`; + case "yum": + return action === "upgrade-all" + ? "yum -y update" + : `yum -y install${target}`; + case "pacman": + return action === "upgrade-all" + ? "pacman -Syu --noconfirm" + : `pacman -S --noconfirm${target}`; + default: + return null; + } +} + +export function buildPackageRemoveCommand( + pkg: PackageManager, + name: string, +): string | null { + switch (pkg) { + case "apt": + return `DEBIAN_FRONTEND=noninteractive apt-get -y remove ${name}`; + case "dnf": + return `dnf -y remove ${name}`; + case "yum": + return `yum -y remove ${name}`; + case "pacman": + return `pacman -R --noconfirm ${name}`; + default: + return null; + } +} + +const PACKAGE_RE = /^[A-Za-z0-9][A-Za-z0-9.+_-]*$/; + +export function isValidPackageName(pkg: unknown): pkg is string { + return typeof pkg === "string" && pkg.length <= 128 && PACKAGE_RE.test(pkg); +} + +export type ElevationErrorCode = "SUDO_REQUIRED" | "SUDO_FAILED" | "NOT_SUDOER"; + +export class ElevationError extends Error { + code: ElevationErrorCode; + constructor(code: ElevationErrorCode, message: string) { + super(message); + this.name = "ElevationError"; + this.code = code; + } +} + +export interface ElevatedResult { + stdout: string; + stderr: string; + code: number | null; + usedSudo: boolean; +} + +const PERMISSION_DENIED = [ + "permission denied", + "operation not permitted", + "must be run as root", + "must be superuser", + "you need to be root", + "are you root", + "access denied", +]; + +/** + * Phrases sudo itself prints to STDERR when authentication or authorization + * fails. Matched only against sudo's own stderr (never command output), so a + * command that happens to print "permission denied" on stdout is not mistaken + * for a sudo failure. + */ +const SUDO_AUTH_FAILED = [ + "incorrect password", + "a password is required", + "a terminal is required", + "no tty present", + "sorry, try again", + "no password was provided", + "1 incorrect password attempt", +]; + +const SUDO_NOT_SUDOER = [ + "is not in the sudoers file", + "not allowed to run sudo", + "not allowed to execute", +]; + +/** + * Marker printed only after sudo has successfully authenticated and started + * the inner shell. Its presence on stdout is the authoritative "elevation + * worked" signal; its absence (together with sudo stderr) means auth failed. + */ +const SUDO_OK_MARKER = "__TX_SUDO_OK__"; +const SUDO_OK_LINE_RE = new RegExp(`^${SUDO_OK_MARKER}\\r?\\n?`); + +/** Escape a value for single-quoted shell context. */ +export function shellSingleQuote(value: string): string { + return `'${value.replace(/'/g, "'\"'\"'")}'`; +} + +/** + * Build the elevated command string: + * echo '' | sudo -S -p '' sh -c 'echo __TX_SUDO_OK__; ' + * + * `-p ''` suppresses the prompt. stderr is NOT merged into stdout, so the + * command's own output stays clean and sudo's auth errors stay on stderr. The + * marker is echoed by the inner shell once sudo authenticates, letting us + * tell a genuine auth failure from command output that merely contains scary + * words. + */ +export function buildSudoCommand( + command: string, + sudoPassword: string, +): string { + const pw = shellSingleQuote(sudoPassword); + const inner = shellSingleQuote(`echo ${SUDO_OK_MARKER}; ${command}`); + return `echo ${pw} | sudo -S -p '' sh -c ${inner}`; +} + +function includesAny(text: string, needles: string[]): boolean { + const lower = text.toLowerCase(); + return needles.some((n) => lower.includes(n)); +} + +function looksLikePermissionDenied(text: string): boolean { + return includesAny(text, PERMISSION_DENIED); +} + +/** Strip the success marker line from the front of stdout. */ +function stripMarker(stdout: string): string { + return stdout.replace(SUDO_OK_LINE_RE, ""); +} + +/** + * Run a command on a client, elevating with the host's stored sudo password + * only when needed (or when forced). Throws a typed `ElevationError` when + * elevation is required but unavailable/incorrect. + */ +export async function execElevated( + client: Client, + command: string, + sudoPassword: string | undefined, + opts: { forceSudo?: boolean; timeoutMs?: number } = {}, +): Promise { + const timeoutMs = opts.timeoutMs ?? 30000; + + if (!opts.forceSudo) { + const direct = await execCommand(client, command, timeoutMs); + if (direct.code === 0) { + return { ...direct, usedSudo: false }; + } + // Only escalate when the failure looks like a privilege problem. A + // permission-denied phrase on stderr (not arbitrary stdout) is the signal. + if (!looksLikePermissionDenied(direct.stderr)) { + return { ...direct, usedSudo: false }; + } + if (!sudoPassword) { + throw new ElevationError( + "SUDO_REQUIRED", + "This action requires elevated privileges. Set a sudo password for this host to continue.", + ); + } + } else if (!sudoPassword) { + throw new ElevationError( + "SUDO_REQUIRED", + "This action requires elevated privileges. Set a sudo password for this host to continue.", + ); + } + + const sudoCmd = buildSudoCommand(command, sudoPassword as string); + const result = await execCommand(client, sudoCmd, timeoutMs); + const authenticated = result.stdout.includes(SUDO_OK_MARKER); + + if (!authenticated) { + // Elevation never started: diagnose from sudo's stderr only. + const notSudoer = includesAny(result.stderr, SUDO_NOT_SUDOER); + const authFailed = includesAny(result.stderr, SUDO_AUTH_FAILED); + if (notSudoer) { + throw new ElevationError( + "NOT_SUDOER", + "The connected user is not permitted to use sudo on this host.", + ); + } + if (authFailed) { + throw new ElevationError( + "SUDO_FAILED", + "Elevation failed. Check the host's sudo password.", + ); + } + // No marker and no recognizable sudo error: treat as a generic failure + // but keep the original output so the caller can surface it. + throw new ElevationError( + "SUDO_FAILED", + "Elevation failed. Check the host's sudo password.", + ); + } + + return { + stdout: stripMarker(result.stdout), + stderr: result.stderr, + code: result.code, + usedSudo: true, + }; +} diff --git a/packages/plugin-sdk/src/testing.ts b/packages/plugin-sdk/src/testing.ts index a75e78e07..ff721abf4 100644 --- a/packages/plugin-sdk/src/testing.ts +++ b/packages/plugin-sdk/src/testing.ts @@ -23,6 +23,11 @@ import type { PluginSecondFactor, PluginSshAuthProvider, PluginSshHost, + PluginHostSummary, + PluginHostAccess, + PluginHostShareResult, + PluginShareableUser, + PluginShareableRole, } from "./backend.js"; import type { SyncEntityRegistration } from "./backend.js"; import type { PluginDatabase } from "./backend.js"; @@ -48,6 +53,11 @@ export interface FakeContextOptions { * when omitted every check passes, so tests that do not care stay simple. */ permissions?: string[]; + /** Hosts ctx.hosts.list/get/checkAccess answer with. */ + hosts?: PluginHostSummary[]; + /** Users and roles ctx.hosts.listUsers/listRoles answer with. */ + shareableUsers?: PluginShareableUser[]; + shareableRoles?: PluginShareableRole[]; } export interface FakeAuthRegistrations { @@ -82,6 +92,13 @@ export interface FakePluginContext { coreSettings: Map; /** Every ctx.ssh.connect and withConnection call, in order. */ sshConnections: Array<{ host: number | PluginSshHost; pool?: string }>; + /** Every ctx.hosts.share call, in order. */ + hostShares: Array<{ + hostId: number; + targets: unknown[]; + permissionLevel: string; + durationHours?: number; + }>; /** Everything registered through ctx.auth. */ auth: FakeAuthRegistrations; /** Changes the acting user, as core's request middleware would. */ @@ -153,6 +170,10 @@ export function createFakeContext( ); const settingsListeners = new Map void>>(); const sshConnections: FakePluginContext["sshConnections"] = []; + const hostShares: FakePluginContext["hostShares"] = []; + const hostsById = new Map( + (options.hosts ?? []).map((h) => [h.id, h]), + ); const services = new Map(); const auth: FakeAuthRegistrations = { sshAuthProviders: [], @@ -385,6 +406,34 @@ export function createFakeContext( disposables, + hosts: { + list: async () => [...hostsById.values()], + get: async (hostId) => hostsById.get(hostId) ?? null, + checkAccess: async (hostId): Promise => { + const found = hostsById.get(hostId); + if (!found) + return { hasAccess: false, isOwner: false, isShared: false }; + const isOwner = found.userId === actor; + return { + hasAccess: true, + isOwner, + isShared: !isOwner, + permissionLevel: "manage", + }; + }, + share: async ( + hostId, + targets, + permissionLevel, + durationHours, + ): Promise => { + hostShares.push({ hostId, targets, permissionLevel, durationHours }); + return { hostId, shared: true }; + }, + listUsers: async () => options.shareableUsers ?? [], + listRoles: async () => options.shareableRoles ?? [], + }, + ssh: { connect: async (host) => { sshConnections.push({ host }); @@ -473,6 +522,7 @@ export function createFakeContext( settings, coreSettings, sshConnections, + hostShares, auth, setActor: (userId) => { actor = userId; @@ -643,6 +693,33 @@ export function createMockCtx( }, }, + hosts: { + list: async () => { + require("hosts:read"); + return ctx.hosts.list(); + }, + get: async (hostId) => { + require("hosts:read"); + return ctx.hosts.get(hostId); + }, + checkAccess: async (hostId, level) => { + require("hosts:read"); + return ctx.hosts.checkAccess(hostId, level); + }, + share: async (hostId, targets, permissionLevel, durationHours) => { + require("hosts:write"); + return ctx.hosts.share(hostId, targets, permissionLevel, durationHours); + }, + listUsers: async () => { + require("hosts:write"); + return ctx.hosts.listUsers(); + }, + listRoles: async () => { + require("hosts:write"); + return ctx.hosts.listRoles(); + }, + }, + ssh: { ...ctx.ssh, connect: async (host, connectOptions) => { diff --git a/plugins/ai/manifest.json b/plugins/ai/manifest.json index 2eedf7dc5..6a42c742b 100644 --- a/plugins/ai/manifest.json +++ b/plugins/ai/manifest.json @@ -32,7 +32,8 @@ "optionalDependencies": { "workspaces": "^1.0.0", "network-topology": "^1.0.0", - "snippets": "^1.0.0" + "snippets": "^1.0.0", + "fleets": "^1.0.0" }, "requires": [ { @@ -49,6 +50,11 @@ "service": "snippets.access", "versionRange": "^1.0.0", "optional": true + }, + { + "service": "fleets.access", + "versionRange": "^1.0.0", + "optional": true } ], "providesSecret": [ diff --git a/plugins/ai/src/backend/services.ts b/plugins/ai/src/backend/services.ts index 566d43bef..163b58aa3 100644 --- a/plugins/ai/src/backend/services.ts +++ b/plugins/ai/src/backend/services.ts @@ -52,6 +52,60 @@ export async function getNetworkTopology( } } +/** Another plugin's fleets, as ctx.services.get("fleets.access") returns them. */ +interface FleetsAccess { + list: () => Promise< + Array<{ id: number; name: string; color: string | null }> + >; + create: (input: { + name: string; + description?: string | null; + }) => Promise<{ id: number; name: string }>; + addMember: (fleetId: number, hostId: number) => Promise; +} + +/** + * The user's fleets, or null when the fleets plugin is off or the user may + * not use it. Optional: the list_fleets tool disappears without it. + */ +export async function listFleets( + userId: string, +): Promise | null> { + if (!current) return null; + try { + return await current.get("fleets.access", { userId }).list(); + } catch { + return null; + } +} + +/** + * Requires the fleets plugin, unlike listFleets above: a proposal apply must + * surface a failure as an error the user sees, not silently no-op. + */ +function requireFleetsAccess(userId: string): FleetsAccess { + if (!current) { + throw new Error("The fleets plugin is disabled"); + } + return current.get("fleets.access", { userId }); +} + +export async function createFleet( + userId: string, + input: { name: string; description?: string | null }, +): Promise<{ id: number; name: string } | null> { + if (!current) return null; + return requireFleetsAccess(userId).create(input); +} + +export async function addFleetMember( + userId: string, + fleetId: number, + hostId: number, +): Promise { + await requireFleetsAccess(userId).addMember(fleetId, hostId); +} + export interface SnippetSummary { id: number; name: string; diff --git a/plugins/ai/src/backend/tools/executor.ts b/plugins/ai/src/backend/tools/executor.ts index 6f732403a..4ea58d519 100644 --- a/plugins/ai/src/backend/tools/executor.ts +++ b/plugins/ai/src/backend/tools/executor.ts @@ -1,6 +1,5 @@ import { createCurrentAutomationRepository, - createCurrentFleetRepository, createCurrentHostRepository, } from "../../../../../src/backend/database/repositories/factory.js"; import { validateDefinition } from "../../../../automations/src/backend/routes.js"; @@ -13,6 +12,8 @@ import { updateSnippet, deleteSnippet, getSnippet, + createFleet, + addFleetMember, } from "../services.js"; /** Approved commands get a bounded window rather than hanging the request. */ @@ -175,10 +176,11 @@ export async function applyProposal( } case "propose_create_fleet": { - const fleet = await createCurrentFleetRepository().create(userId, { + const fleet = await createFleet(userId, { name: requireString(payload.name, "name"), description: optionalString(payload.description), - } as any); + }); + if (!fleet) throw new Error("The fleets plugin is disabled"); const hostIds = Array.isArray(payload.hostIds) ? payload.hostIds : []; let added = 0; @@ -191,16 +193,13 @@ export async function applyProposal( hostId, ); if (!host) continue; - await createCurrentFleetRepository().addMember( - (fleet as any).id, - hostId, - ); + await addFleetMember(userId, fleet.id, hostId); added += 1; } return { ok: true, - summary: `Created fleet ${(fleet as any).name} with ${added} host${added === 1 ? "" : "s"}`, + summary: `Created fleet ${fleet.name} with ${added} host${added === 1 ? "" : "s"}`, }; } diff --git a/plugins/ai/src/backend/tools/read-tools.ts b/plugins/ai/src/backend/tools/read-tools.ts index e585a1769..d9d3d4552 100644 --- a/plugins/ai/src/backend/tools/read-tools.ts +++ b/plugins/ai/src/backend/tools/read-tools.ts @@ -2,7 +2,6 @@ import { createCurrentNotificationChannelRepository, createCurrentAutomationRepository, createCurrentCommandHistoryRepository, - createCurrentFleetRepository, createCurrentHomepageItemRepository, createCurrentHostRepository, } from "../../../../../src/backend/database/repositories/factory.js"; @@ -11,6 +10,7 @@ import { listSavedWorkspaces, getNetworkTopology, listSnippets, + listFleets, } from "../services.js"; /** @@ -87,18 +87,16 @@ export const readTools: AiTool[] = [ { name: "list_fleets", description: - "List the user's fleets. Fleets group hosts for bulk operations and inventory.", + "List the user's fleets. Fleets group hosts for bulk operations and inventory. Unavailable if the fleets plugin is disabled.", category: "read", parameters: objectSchema({}), handler: async (_args, context) => { - const fleets = await createCurrentFleetRepository().listByUser( - context.userId, - ); + const fleets = await listFleets(context.userId); + if (fleets === null) return { unavailable: true, fleets: [] }; return { - fleets: fleets.map((fleet: any) => ({ + fleets: fleets.map((fleet) => ({ id: fleet.id, name: fleet.name, - description: fleet.description ?? null, })), }; }, diff --git a/plugins/ai/tests/backend/tools/proposal-executor.test.ts b/plugins/ai/tests/backend/tools/proposal-executor.test.ts index 796742e46..1c112c189 100644 --- a/plugins/ai/tests/backend/tools/proposal-executor.test.ts +++ b/plugins/ai/tests/backend/tools/proposal-executor.test.ts @@ -12,13 +12,12 @@ const snippetRepository = { updateSnippet: vi.fn(), deleteSnippet: vi.fn(), }; -const fleetRepository = { create: vi.fn(), addMember: vi.fn() }; const automationRepository = { create: vi.fn() }; +const fleetsAccess = { create: vi.fn(), addMember: vi.fn() }; vi.mock("../../../../../src/backend/database/repositories/factory.js", () => ({ createCurrentHostRepository: () => hostRepository, createCurrentSnippetRepository: () => snippetRepository, - createCurrentFleetRepository: () => fleetRepository, createCurrentAutomationRepository: () => automationRepository, })); @@ -44,10 +43,12 @@ vi.mock("../../../src/backend/ssh.js", () => ({ const { applyProposal } = await import("../../../src/backend/tools/executor.js"); +const { setPluginServices } = await import("../../../src/backend/services.js"); describe("applyProposal", () => { beforeEach(() => { vi.clearAllMocks(); + setPluginServices(null); }); it("refuses a kind that is not a real tool", async () => { @@ -221,7 +222,15 @@ describe("applyProposal", () => { }); it("only adds fleet members the approving user owns", async () => { - fleetRepository.create.mockResolvedValue({ id: 3, name: "prod" }); + setPluginServices({ + get: (service: string) => { + if (service !== "fleets.access") + throw new Error(`unexpected ${service}`); + return fleetsAccess as never; + }, + provide: vi.fn(), + } as never); + fleetsAccess.create.mockResolvedValue({ id: 3, name: "prod" }); hostRepository.findByIdForUser.mockImplementation( async (_userId: string, hostId: number) => hostId === 1 ? { id: 1 } : null, @@ -233,11 +242,17 @@ describe("applyProposal", () => { "user-1", ); - expect(fleetRepository.addMember).toHaveBeenCalledTimes(1); - expect(fleetRepository.addMember).toHaveBeenCalledWith(3, 1); + expect(fleetsAccess.addMember).toHaveBeenCalledTimes(1); + expect(fleetsAccess.addMember).toHaveBeenCalledWith(3, 1); expect(result.summary).toContain("1 host"); }); + it("fails clearly when the fleets plugin is disabled", async () => { + await expect( + applyProposal("propose_create_fleet", { name: "prod" }, "user-1"), + ).rejects.toThrow("fleets plugin is disabled"); + }); + it("reports nothing to change on an empty update", async () => { hostRepository.findByIdForUser.mockResolvedValue({ id: 7 }); diff --git a/plugins/automations/manifest.json b/plugins/automations/manifest.json index b25fd90e9..93fad7079 100644 --- a/plugins/automations/manifest.json +++ b/plugins/automations/manifest.json @@ -27,13 +27,19 @@ "ui:surface" ], "optionalDependencies": { - "snippets": "^1.0.0" + "snippets": "^1.0.0", + "fleets": "^1.0.0" }, "requires": [ { "service": "snippets.access", "versionRange": "^1.0.0", "optional": true + }, + { + "service": "fleets.access", + "versionRange": "^1.0.0", + "optional": true } ], "contributes": { diff --git a/plugins/automations/src/backend/actions/host-targets.ts b/plugins/automations/src/backend/actions/host-targets.ts index 95cdc832b..62a62f29b 100644 --- a/plugins/automations/src/backend/actions/host-targets.ts +++ b/plugins/automations/src/backend/actions/host-targets.ts @@ -1,7 +1,7 @@ import type { HostSelector } from "../../../../../src/types/automations.js"; import { resolveHostById } from "../../../../../src/backend/hosts/host-resolver.js"; -import { createCurrentFleetRepository } from "../../../../../src/backend/database/repositories/factory.js"; import { PermissionManager } from "../../../../../src/backend/utils/permission-manager.js"; +import { fleetMemberIds } from "../snippets.js"; import type { StepExecutionContext } from "./types.js"; /** A host the caller is allowed to act on. `host` is always resolved. */ @@ -63,13 +63,7 @@ async function fleetHostIds( fleetId: number, userId: string, ): Promise { - try { - const repository = createCurrentFleetRepository(); - const members = await repository.listEffectiveMembers(userId, fleetId); - return members.map((member) => member.id); - } catch { - return []; - } + return fleetMemberIds(userId, fleetId); } async function allAccessibleHostIds(userId: string): Promise { diff --git a/plugins/automations/src/backend/snippets.ts b/plugins/automations/src/backend/snippets.ts index db77901b3..e86dceb99 100644 --- a/plugins/automations/src/backend/snippets.ts +++ b/plugins/automations/src/backend/snippets.ts @@ -62,3 +62,30 @@ export async function getSnippet( return null; } } + +/** Another plugin's fleets, as ctx.services.get("fleets.access") returns them. */ +interface FleetsAccess { + members: ( + fleetId: number, + ) => Promise>; +} + +/** + * A fleet's effective member host ids, for a "fleet" target selector. Empty + * when the fleets plugin is off, the user may not use it, or the fleet does + * not exist - the step then runs against no hosts rather than failing. + */ +export async function fleetMemberIds( + userId: string, + fleetId: number, +): Promise { + if (!current) return []; + try { + const members = await current + .get("fleets.access", { userId }) + .members(fleetId); + return members.map((member) => member.id); + } catch { + return []; + } +} diff --git a/plugins/fleets/locales/en.json b/plugins/fleets/locales/en.json index 3b7aeddda..0fe7a94dc 100644 --- a/plugins/fleets/locales/en.json +++ b/plugins/fleets/locales/en.json @@ -1,4 +1,18 @@ { + "permissions": { + "view": { + "title": "View fleets", + "description": "See fleets, their members and inventory." + }, + "manage": { + "title": "Manage fleets", + "description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles." + }, + "execute": { + "title": "Run fleet actions", + "description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts." + } + }, "newUi": { "sidebar": { "fleets": { diff --git a/plugins/fleets/manifest.json b/plugins/fleets/manifest.json index a4d831057..152a0381b 100644 --- a/plugins/fleets/manifest.json +++ b/plugins/fleets/manifest.json @@ -16,12 +16,20 @@ }, "capabilities": [ "hosts:read", + "hosts:write", "credentials:use", "ssh:connect", "db:own", "network:serve", "ui:surface" ], + "provides": [ + { + "service": "fleets.access", + "version": "1.0.0", + "permission": "fleets.view" + } + ], "locales": "locales", "platforms": ["linux", "win32", "darwin"], "contributes": { @@ -39,6 +47,26 @@ "titleKey": "nav.fleets", "icon": "Boxes" } + ], + "permissions": [ + { + "name": "view", + "titleKey": "permissions.view.title", + "descriptionKey": "permissions.view.description", + "defaultRoles": ["user"] + }, + { + "name": "manage", + "titleKey": "permissions.manage.title", + "descriptionKey": "permissions.manage.description", + "defaultRoles": ["user"] + }, + { + "name": "execute", + "titleKey": "permissions.execute.title", + "descriptionKey": "permissions.execute.description", + "defaultRoles": ["user"] + } ] } } diff --git a/plugins/fleets/migrations/mysql/0001_adopt_fleets.sql b/plugins/fleets/migrations/mysql/0001_adopt_fleets.sql new file mode 100644 index 000000000..b4438da9a --- /dev/null +++ b/plugins/fleets/migrations/mysql/0001_adopt_fleets.sql @@ -0,0 +1,42 @@ +-- fleets 0001: adopt_fleets +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS `fleets` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `name` text NOT NULL, + `description` text, + `color` text, + `icon` text, + `tag_rules` text, + `sync_id` varchar(255) UNIQUE, + `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +); +RENAME TABLE `fleets` TO `p_fleets_fleets`; +CREATE TABLE IF NOT EXISTS `fleet_members` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `fleet_id` int NOT NULL, + `host_id` int NOT NULL, + `added_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`fleet_id`) REFERENCES `p_fleets_fleets` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE +); +RENAME TABLE `fleet_members` TO `p_fleets_members`; +CREATE TABLE IF NOT EXISTS `fleet_inventory` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `host_id` int NOT NULL, + `user_id` varchar(255) NOT NULL, + `os_pretty_name` text, + `kernel` text, + `architecture` text, + `hostname` text, + `uptime_seconds` int, + `ip` text, + `package_manager` text, + `collected_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +); +RENAME TABLE `fleet_inventory` TO `p_fleets_inventory`; diff --git a/plugins/fleets/migrations/postgres/0001_adopt_fleets.sql b/plugins/fleets/migrations/postgres/0001_adopt_fleets.sql new file mode 100644 index 000000000..d54bd57f9 --- /dev/null +++ b/plugins/fleets/migrations/postgres/0001_adopt_fleets.sql @@ -0,0 +1,46 @@ +-- fleets 0001: adopt_fleets +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "fleets" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "name" text NOT NULL, + "description" text, + "color" text, + "icon" text, + "tag_rules" text, + "sync_id" varchar(255) UNIQUE, + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE +); +ALTER TABLE "fleets" RENAME TO "p_fleets_fleets"; +CREATE TABLE IF NOT EXISTS "fleet_members" ( + "id" serial PRIMARY KEY, + "fleet_id" integer NOT NULL, + "host_id" integer NOT NULL, + "added_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("fleet_id") REFERENCES "p_fleets_fleets" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_members_fleet_host" ON "fleet_members" ("fleet_id", "host_id"); +CREATE INDEX IF NOT EXISTS "idx_fleet_members_host" ON "fleet_members" ("host_id"); +ALTER TABLE "fleet_members" RENAME TO "p_fleets_members"; +CREATE TABLE IF NOT EXISTS "fleet_inventory" ( + "id" serial PRIMARY KEY, + "host_id" integer NOT NULL, + "user_id" varchar(255) NOT NULL, + "os_pretty_name" text, + "kernel" text, + "architecture" text, + "hostname" text, + "uptime_seconds" integer, + "ip" text, + "package_manager" text, + "collected_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE +); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_inventory_host" ON "fleet_inventory" ("host_id", "user_id"); +CREATE INDEX IF NOT EXISTS "idx_fleet_inventory_user" ON "fleet_inventory" ("user_id"); +ALTER TABLE "fleet_inventory" RENAME TO "p_fleets_inventory"; diff --git a/plugins/fleets/migrations/snapshot.json b/plugins/fleets/migrations/snapshot.json new file mode 100644 index 000000000..d00160606 --- /dev/null +++ b/plugins/fleets/migrations/snapshot.json @@ -0,0 +1,141 @@ +{ + "version": 1, + "tables": { + "fleets": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "name": { + "type": "text", + "notNull": true + }, + "description": { + "type": "text" + }, + "color": { + "type": "text" + }, + "icon": { + "type": "text" + }, + "tagRules": { + "type": "text" + }, + "syncId": { + "type": "text", + "unique": true + }, + "createdAt": { + "type": "text", + "notNull": true, + "defaultNow": true + }, + "updatedAt": { + "type": "text", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [], + "adopts": "fleets" + }, + "members": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "fleetId": { + "type": "integer", + "notNull": true + }, + "hostId": { + "type": "refHost", + "notNull": true + }, + "addedAt": { + "type": "text", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [ + { + "name": "idx_fleet_members_fleet_host", + "columns": ["fleetId", "hostId"], + "unique": true + }, + { + "name": "idx_fleet_members_host", + "columns": ["hostId"] + } + ], + "adopts": "fleet_members" + }, + "inventory": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "hostId": { + "type": "refHost", + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "osPrettyName": { + "type": "text" + }, + "kernel": { + "type": "text" + }, + "architecture": { + "type": "text" + }, + "hostname": { + "type": "text" + }, + "uptimeSeconds": { + "type": "integer" + }, + "ip": { + "type": "text" + }, + "packageManager": { + "type": "text" + }, + "collectedAt": { + "type": "text", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [ + { + "name": "idx_fleet_inventory_host", + "columns": ["hostId", "userId"], + "unique": true + }, + { + "name": "idx_fleet_inventory_user", + "columns": ["userId"] + } + ], + "adopts": "fleet_inventory" + } + } +} diff --git a/plugins/fleets/migrations/sqlite/0001_adopt_fleets.sql b/plugins/fleets/migrations/sqlite/0001_adopt_fleets.sql new file mode 100644 index 000000000..c7d6c47b3 --- /dev/null +++ b/plugins/fleets/migrations/sqlite/0001_adopt_fleets.sql @@ -0,0 +1,46 @@ +-- fleets 0001: adopt_fleets +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "fleets" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "name" text NOT NULL, + "description" text, + "color" text, + "icon" text, + "tag_rules" text, + "sync_id" text UNIQUE, + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE +); +ALTER TABLE "fleets" RENAME TO "p_fleets_fleets"; +CREATE TABLE IF NOT EXISTS "fleet_members" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "fleet_id" integer NOT NULL, + "host_id" integer NOT NULL, + "added_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("fleet_id") REFERENCES "p_fleets_fleets" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_members_fleet_host" ON "fleet_members" ("fleet_id", "host_id"); +CREATE INDEX IF NOT EXISTS "idx_fleet_members_host" ON "fleet_members" ("host_id"); +ALTER TABLE "fleet_members" RENAME TO "p_fleets_members"; +CREATE TABLE IF NOT EXISTS "fleet_inventory" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "host_id" integer NOT NULL, + "user_id" text NOT NULL, + "os_pretty_name" text, + "kernel" text, + "architecture" text, + "hostname" text, + "uptime_seconds" integer, + "ip" text, + "package_manager" text, + "collected_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE +); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_inventory_host" ON "fleet_inventory" ("host_id", "user_id"); +CREATE INDEX IF NOT EXISTS "idx_fleet_inventory_user" ON "fleet_inventory" ("user_id"); +ALTER TABLE "fleet_inventory" RENAME TO "p_fleets_inventory"; diff --git a/plugins/fleets/src/backend/index.ts b/plugins/fleets/src/backend/index.ts index 7a86375e0..f4dfcd2a2 100644 --- a/plugins/fleets/src/backend/index.ts +++ b/plugins/fleets/src/backend/index.ts @@ -1,14 +1,75 @@ +import type { Router } from "express"; import type { PluginContext } from "@termix/plugin-sdk/backend"; -import { setPluginSsh } from "./ssh.js"; -import { startFleetsService, stopFleetsService } from "./routes.js"; +import { fleets, fleetMembers, fleetInventory } from "./tables.js"; +import { createFleetRepository } from "./repository.js"; +import { registerFleetRoutes } from "./routes.js"; + +/** What other plugins get from ctx.services.get("fleets.access", {userId}). */ +export interface FleetsService { + /** The calling user's fleets, without resolving membership. */ + list: () => Promise< + Array<{ id: number; name: string; color: string | null }> + >; + /** A fleet's effective member hosts (static membership union tag matches). */ + members: ( + fleetId: number, + ) => Promise>; + /** Creates a fleet owned by the calling user. */ + create: (input: { + name: string; + description?: string | null; + }) => Promise<{ id: number; name: string }>; + /** Adds a host the calling user owns to a fleet's static membership. */ + addMember: (fleetId: number, hostId: number) => Promise; +} export async function activate(ctx: PluginContext) { - setPluginSsh(ctx.ssh); - ctx.disposables.add(() => setPluginSsh(null)); - startFleetsService(ctx.http.router()); + const fleetsTable = await ctx.db.define(fleets); + const membersTable = await ctx.db.define(fleetMembers); + const inventoryTable = await ctx.db.define(fleetInventory); + + const repo = createFleetRepository( + ctx.db, + ctx.hosts, + fleetsTable, + membersTable, + inventoryTable, + ); + + registerFleetRoutes(ctx.http.router(), repo, ctx); + + const service: FleetsService = { + list: async () => { + const userId = ctx.currentActor(); + if (!userId) return []; + const rows = await repo.listByUser(userId); + return rows.map((f) => ({ id: f.id, name: f.name, color: f.color })); + }, + members: async (fleetId) => { + const userId = ctx.currentActor(); + if (!userId) return []; + const members = await repo.listEffectiveMembers(userId, fleetId); + return members.map((m) => ({ id: m.id, name: m.name, ip: m.ip })); + }, + create: async (input) => { + const userId = ctx.currentActor(); + if (!userId) throw new Error("fleets.access.create needs an actor"); + const created = await repo.create(userId, input); + return { id: created.id, name: created.name }; + }, + addMember: async (fleetId, hostId) => { + const userId = ctx.currentActor(); + if (!userId) throw new Error("fleets.access.addMember needs an actor"); + const fleet = await repo.findById(userId, fleetId); + if (!fleet) throw new Error("Fleet not found"); + await repo.addMember(fleetId, hostId); + }, + }; + ctx.services.provide("fleets.access", service); + ctx.log.info("Fleets routes mounted at /plugin-api/fleets"); } export async function deactivate() { - stopFleetsService(); + // Everything above was registered through ctx and is disposed by core. } diff --git a/plugins/fleets/src/backend/repository.ts b/plugins/fleets/src/backend/repository.ts new file mode 100644 index 000000000..79cabbb63 --- /dev/null +++ b/plugins/fleets/src/backend/repository.ts @@ -0,0 +1,370 @@ +import { randomUUID } from "node:crypto"; +import { and, eq, inArray } from "drizzle-orm"; +import type { PluginDatabase, PluginHosts } from "@termix/plugin-sdk/backend"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +// The tables come from ctx.db.define, which the SDK hands back untyped, and +// the drizzle handle is the server's own. Typed at this module's edge instead. +type Table = any; +type Drizzle = any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +export interface FleetRecord { + id: number; + userId: string; + name: string; + description: string | null; + color: string | null; + icon: string | null; + tagRules: string | null; + syncId: string | null; + createdAt: string; + updatedAt: string; +} + +export interface FleetCreateInput { + name: string; + description?: string | null; + color?: string | null; + icon?: string | null; + tagRules?: string[]; +} + +export interface FleetUpdateInput { + name?: string; + description?: string | null; + color?: string | null; + icon?: string | null; + tagRules?: string[]; +} + +export interface FleetInventoryRecord { + id: number; + hostId: number; + userId: string; + osPrettyName: string | null; + kernel: string | null; + architecture: string | null; + hostname: string | null; + uptimeSeconds: number | null; + ip: string | null; + packageManager: string | null; + collectedAt: string; +} + +export interface FleetInventoryInput { + osPrettyName: string | null; + kernel: string | null; + architecture: string | null; + hostname: string | null; + uptimeSeconds: number | null; + ip: string | null; + packageManager: string | null; +} + +function parseTagRules(raw: string | null): string[] { + if (!raw) return []; + try { + const parsed = JSON.parse(raw); + return Array.isArray(parsed) + ? parsed.filter((t): t is string => typeof t === "string") + : []; + } catch { + return []; + } +} + +export type FleetRepository = ReturnType; + +/** + * Fleets, membership and inventory, written without RETURNING so it runs the + * same on all three engines: an insert is read back by its sync id, an + * update by its own id. + */ +export function createFleetRepository( + db: PluginDatabase, + hosts: PluginHosts, + fleetsTable: Table, + membersTable: Table, + inventoryTable: Table, +) { + const client = () => db.client(); + + async function findById( + userId: string, + fleetId: number, + ): Promise { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(fleetsTable) + .where(and(eq(fleetsTable.id, fleetId), eq(fleetsTable.userId, userId))) + .limit(1); + return (rows[0] as FleetRecord) ?? null; + } + + async function findBySyncId(syncId: string): Promise { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(fleetsTable) + .where(eq(fleetsTable.syncId, syncId)) + .limit(1); + return rows[0] as FleetRecord; + } + + async function listStaticMemberIds(fleetId: number): Promise { + const drizzle = await client(); + const rows = await drizzle + .select({ hostId: membersTable.hostId }) + .from(membersTable) + .where(eq(membersTable.fleetId, fleetId)); + return rows.map((r: { hostId: number }) => r.hostId); + } + + return { + async listByUser(userId: string): Promise { + const drizzle = await client(); + return drizzle + .select() + .from(fleetsTable) + .where(eq(fleetsTable.userId, userId)); + }, + + findById, + listStaticMemberIds, + + async create( + userId: string, + input: FleetCreateInput, + now = new Date().toISOString(), + ): Promise { + const syncId = randomUUID(); + const drizzle = await client(); + await drizzle.insert(fleetsTable).values({ + userId, + name: input.name, + description: input.description ?? null, + color: input.color ?? null, + icon: input.icon ?? null, + tagRules: input.tagRules ? JSON.stringify(input.tagRules) : null, + syncId, + createdAt: now, + updatedAt: now, + }); + await db.persist(); + return findBySyncId(syncId); + }, + + async update( + userId: string, + fleetId: number, + input: FleetUpdateInput, + now = new Date().toISOString(), + ): Promise { + const existing = await findById(userId, fleetId); + if (!existing) return null; + + const drizzle = await client(); + await drizzle + .update(fleetsTable) + .set({ + name: input.name ?? existing.name, + description: + input.description === undefined + ? existing.description + : input.description, + color: input.color === undefined ? existing.color : input.color, + icon: input.icon === undefined ? existing.icon : input.icon, + tagRules: + input.tagRules === undefined + ? existing.tagRules + : JSON.stringify(input.tagRules), + updatedAt: now, + }) + .where( + and(eq(fleetsTable.id, fleetId), eq(fleetsTable.userId, userId)), + ); + await db.persist(); + return findById(userId, fleetId); + }, + + async delete(userId: string, fleetId: number): Promise { + const existing = await findById(userId, fleetId); + if (!existing) return false; + const drizzle = await client(); + await drizzle + .delete(fleetsTable) + .where( + and(eq(fleetsTable.id, fleetId), eq(fleetsTable.userId, userId)), + ); + await db.persist(); + return true; + }, + + async addMember( + fleetId: number, + hostId: number, + now = new Date().toISOString(), + ): Promise { + const drizzle = await client(); + const existing = await drizzle + .select({ id: membersTable.id }) + .from(membersTable) + .where( + and( + eq(membersTable.fleetId, fleetId), + eq(membersTable.hostId, hostId), + ), + ) + .limit(1); + if (existing.length > 0) return; + + await drizzle + .insert(membersTable) + .values({ fleetId, hostId, addedAt: now }); + await db.persist(); + }, + + async removeMember(fleetId: number, hostId: number): Promise { + const drizzle = await client(); + const existing = await drizzle + .select({ id: membersTable.id }) + .from(membersTable) + .where( + and( + eq(membersTable.fleetId, fleetId), + eq(membersTable.hostId, hostId), + ), + ) + .limit(1); + if (existing.length === 0) return false; + + await drizzle + .delete(membersTable) + .where( + and( + eq(membersTable.fleetId, fleetId), + eq(membersTable.hostId, hostId), + ), + ); + await db.persist(); + return true; + }, + + /** + * Effective membership = static members rows union hosts whose + * comma-separated tags string intersects any tag in the fleet's + * tagRules. Both sets are scoped to hosts the fleet owner can see + * (ctx.hosts.list already resolves owner-only, since a fleet only holds + * one user's hosts) and deduplicated by id. + */ + async listEffectiveMembers(userId: string, fleetId: number) { + const fleet = await findById(userId, fleetId); + if (!fleet) return []; + + const staticIds = await listStaticMemberIds(fleetId); + const tagRules = parseTagRules(fleet.tagRules); + + const ownedHosts = (await hosts.list()).filter( + (host) => host.userId === userId, + ); + + const byId = new Map(); + for (const host of ownedHosts) { + if (staticIds.includes(host.id)) { + byId.set(host.id, host); + continue; + } + if (tagRules.length === 0) continue; + const hostTags = (host.tags ?? "") + .split(",") + .map((t) => t.trim()) + .filter(Boolean); + if (hostTags.some((tag) => tagRules.includes(tag))) { + byId.set(host.id, host); + } + } + + return Array.from(byId.values()); + }, + + async deleteByUserId(userId: string): Promise { + const drizzle = await client(); + const userFleets = await drizzle + .select({ id: fleetsTable.id }) + .from(fleetsTable) + .where(eq(fleetsTable.userId, userId)); + if (userFleets.length === 0) return 0; + await drizzle.delete(fleetsTable).where(eq(fleetsTable.userId, userId)); + await db.persist(); + return userFleets.length; + }, + + async upsertInventory( + userId: string, + hostId: number, + input: FleetInventoryInput, + now = new Date().toISOString(), + ): Promise { + const drizzle = await client(); + const existing = await drizzle + .select() + .from(inventoryTable) + .where( + and( + eq(inventoryTable.hostId, hostId), + eq(inventoryTable.userId, userId), + ), + ) + .limit(1); + + if (existing[0]) { + await drizzle + .update(inventoryTable) + .set({ ...input, collectedAt: now }) + .where(eq(inventoryTable.id, existing[0].id)); + await db.persist(); + const [updated] = await drizzle + .select() + .from(inventoryTable) + .where(eq(inventoryTable.id, existing[0].id)) + .limit(1); + return updated as FleetInventoryRecord; + } + + await drizzle + .insert(inventoryTable) + .values({ userId, hostId, ...input, collectedAt: now }); + await db.persist(); + const [created] = await drizzle + .select() + .from(inventoryTable) + .where( + and( + eq(inventoryTable.hostId, hostId), + eq(inventoryTable.userId, userId), + ), + ) + .limit(1); + return created as FleetInventoryRecord; + }, + + async listInventoryForHosts( + userId: string, + hostIds: number[], + ): Promise { + if (hostIds.length === 0) return []; + const drizzle = await client(); + return drizzle + .select() + .from(inventoryTable) + .where( + and( + eq(inventoryTable.userId, userId), + inArray(inventoryTable.hostId, hostIds), + ), + ); + }, + }; +} diff --git a/plugins/fleets/src/backend/routes.ts b/plugins/fleets/src/backend/routes.ts index fb0446aea..3611cd530 100644 --- a/plugins/fleets/src/backend/routes.ts +++ b/plugins/fleets/src/backend/routes.ts @@ -1,50 +1,23 @@ -import { getErrorMessage } from "../../../../src/backend/utils/error-message.js"; -import type { AuthenticatedRequest } from "../../../../src/types/index.js"; -import express, { type Request, type Response, type Router } from "express"; +import type { Request, Response, Router } from "express"; import multer from "multer"; import JSZip from "jszip"; import type { Client, SFTPWrapper } from "ssh2"; +import type { + PluginContext, + PluginHostShareLevel, +} from "@termix/plugin-sdk/backend"; import { - authLogger, - databaseLogger, -} from "../../../../src/backend/utils/logger.js"; -import { AuthManager } from "../../../../src/backend/utils/auth-manager.js"; -import { - PermissionManager, - type HostAction, -} from "../../../../src/backend/utils/permission-manager.js"; -import { - isSharePermissionLevel, - expiryFromDuration, - parseShareTargets, -} from "../../../../src/backend/database/routes/rbac.js"; -import { - createCurrentFleetRepository, - createCurrentFleetInventoryRepository, - createCurrentRbacAccessRepository, - createCurrentRoleRepository, - createCurrentUserRepository, -} from "../../../../src/backend/database/repositories/factory.js"; -import { resolveHostById } from "../../../../src/backend/hosts/host-resolver.js"; -import { withSshConnection } from "./ssh.js"; -import { execCommand } from "../../../../src/backend/hosts/metrics-shared/common-utils.js"; -import { detectPlatform } from "../../../../src/backend/hosts/metrics-shared/platform.js"; -import { + execCommand, + detectPlatform, + buildPackageActionCommand, + buildPackageRemoveCommand, + isValidPackageName, execElevated, ElevationError, -} from "../../../../src/backend/hosts/metrics-shared/exec-elevated.js"; -import { buildPackageActionCommand } from "../../../../src/backend/hosts/metrics-shared/package-commands.js"; -import { isValidPackageName } from "../../../../src/backend/hosts/metrics-shared/validation.js"; +} from "@termix/plugin-sdk/host-commands"; +import type { FleetRepository } from "./repository.js"; import { resolveCommandVariables } from "./command-variables.js"; -export const router = express.Router(); - -const authManager = AuthManager.getInstance(); -const permissionManager = PermissionManager.getInstance(); - -const authenticateJWT = authManager.createAuthMiddleware(); -const requireDataAccess = authManager.createDataAccessMiddleware(); - const FLEET_TRANSFER_MAX_BYTES = 200 * 1024 * 1024; const upload = multer({ storage: multer.memoryStorage(), @@ -60,676 +33,63 @@ function parseFleetId(raw: unknown): number | null { return Number.isInteger(id) ? id : null; } -/** - * @openapi - * /fleets: - * get: - * summary: List the current user's fleets - * tags: - * - Fleets - * responses: - * 200: - * description: List of fleets with member counts. - */ -router.get( - "/", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; +const SHARE_LEVELS: PluginHostShareLevel[] = [ + "connect", + "view", + "edit", + "manage", +]; - try { - const fleetRepository = createCurrentFleetRepository(); - const fleetList = await fleetRepository.listByUser(userId); +function isShareLevel(value: unknown): value is PluginHostShareLevel { + return SHARE_LEVELS.includes(value as PluginHostShareLevel); +} - const withCounts = await Promise.all( - fleetList.map(async (fleet) => { - const members = await fleetRepository.listEffectiveMembers( - userId, - fleet.id, - ); - return { - ...fleet, - tagRules: fleet.tagRules ? JSON.parse(fleet.tagRules) : [], - memberCount: members.length, - }; - }), - ); +interface ShareTargetInput { + type: "user" | "role"; + id: string | number; +} - res.json(withCounts); - } catch (err) { - databaseLogger.error("Failed to list fleets", err, { - operation: "fleet_list_failed", - userId, - }); - res.status(500).json({ error: "Failed to list fleets" }); - } - }, -); +function parseShareTargets( + body: Record, +): ShareTargetInput[] | null { + const rawTargets = body.targets; + if (!Array.isArray(rawTargets) || rawTargets.length === 0) return null; -/** - * @openapi - * /fleets: - * post: - * summary: Create a fleet - * tags: - * - Fleets - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * description: - * type: string - * color: - * type: string - * icon: - * type: string - * tagRules: - * type: array - * items: - * type: string - * responses: - * 200: - * description: Fleet created. - * 400: - * description: Invalid request body. - */ -router.post( - "/", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { name, description, color, icon, tagRules } = req.body ?? {}; - - if (!isNonEmptyString(name)) { - return res.status(400).json({ error: "Fleet name is required" }); - } - - if ( - tagRules !== undefined && - (!Array.isArray(tagRules) || tagRules.some((t) => typeof t !== "string")) + const targets: ShareTargetInput[] = []; + for (const raw of rawTargets) { + if (!raw || typeof raw !== "object") return null; + const { type, id } = raw as { type?: unknown; id?: unknown }; + if (type === "user" && isNonEmptyString(id)) { + targets.push({ type: "user", id }); + } else if ( + type === "role" && + typeof id === "number" && + Number.isInteger(id) ) { - return res - .status(400) - .json({ error: "tagRules must be an array of strings" }); + targets.push({ type: "role", id }); + } else { + return null; } + } + return targets; +} - try { - const fleet = await createCurrentFleetRepository().create(userId, { - name: name.trim(), - description: isNonEmptyString(description) ? description.trim() : null, - color: isNonEmptyString(color) ? color : null, - icon: isNonEmptyString(icon) ? icon : null, - tagRules, - }); +function actor(ctx: PluginContext): string { + return ctx.currentActor() as string; +} - authLogger.success(`Fleet created: ${fleet.name}`, { - operation: "fleet_create_success", - userId, - fleetId: fleet.id, - }); +function logError( + ctx: PluginContext, + message: string, + error: unknown, + operation: string, +): void { + ctx.log.error( + `${message} (${operation})`, + error instanceof Error ? error : new Error(String(error)), + ); +} - res.json({ ...fleet, tagRules: tagRules ?? [] }); - } catch (err) { - databaseLogger.error("Failed to create fleet", err, { - operation: "fleet_create_failed", - userId, - }); - res.status(500).json({ error: "Failed to create fleet" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}: - * patch: - * summary: Update a fleet - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Fleet updated. - * 404: - * description: Fleet not found. - */ -router.patch( - "/:id", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - - const { name, description, color, icon, tagRules } = req.body ?? {}; - - if (name !== undefined && !isNonEmptyString(name)) { - return res.status(400).json({ error: "Fleet name cannot be empty" }); - } - - if ( - tagRules !== undefined && - (!Array.isArray(tagRules) || tagRules.some((t) => typeof t !== "string")) - ) { - return res - .status(400) - .json({ error: "tagRules must be an array of strings" }); - } - - try { - const updated = await createCurrentFleetRepository().update( - userId, - fleetId, - { - name: name !== undefined ? name.trim() : undefined, - description, - color, - icon, - tagRules, - }, - ); - - if (!updated) { - return res.status(404).json({ error: "Fleet not found" }); - } - - res.json({ - ...updated, - tagRules: updated.tagRules ? JSON.parse(updated.tagRules) : [], - }); - } catch (err) { - databaseLogger.error("Failed to update fleet", err, { - operation: "fleet_update_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to update fleet" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}: - * delete: - * summary: Delete a fleet - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Fleet deleted. - * 404: - * description: Fleet not found. - */ -router.delete( - "/:id", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - - try { - const deleted = await createCurrentFleetRepository().delete( - userId, - fleetId, - ); - if (!deleted) { - return res.status(404).json({ error: "Fleet not found" }); - } - res.json({ success: true }); - } catch (err) { - databaseLogger.error("Failed to delete fleet", err, { - operation: "fleet_delete_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to delete fleet" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}/members: - * get: - * summary: List the resolved effective members of a fleet - * description: Returns the union of statically-added hosts and hosts matched by the fleet's tag rules, each annotated with the caller's permission level on that host. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Resolved member hosts. - * 404: - * description: Fleet not found. - */ -router.get( - "/:id/members", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - - try { - const fleetRepository = createCurrentFleetRepository(); - const fleet = await fleetRepository.findById(userId, fleetId); - if (!fleet) { - return res.status(404).json({ error: "Fleet not found" }); - } - - const staticIds = await fleetRepository.listStaticMemberIds(fleetId); - const staticIdSet = new Set(staticIds); - const members = await fleetRepository.listEffectiveMembers( - userId, - fleetId, - ); - - const annotated = await Promise.all( - members.map(async (host) => { - const access = await permissionManager.canAccessHost( - userId, - host.id, - "connect", - ); - return { - id: host.id, - name: host.name, - ip: host.ip, - tags: host.tags ? host.tags.split(",").filter(Boolean) : [], - static: staticIdSet.has(host.id), - permissionLevel: access.isOwner - ? "manage" - : (access.permissionLevel ?? null), - }; - }), - ); - - res.json(annotated); - } catch (err) { - databaseLogger.error("Failed to list fleet members", err, { - operation: "fleet_members_list_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to list fleet members" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}/members: - * post: - * summary: Add a host to a fleet's static membership - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: number - * responses: - * 200: - * description: Host added. - * 404: - * description: Fleet or host not found. - */ -router.post( - "/:id/members", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - const hostId = Number(req.body?.hostId); - - if (fleetId === null || !Number.isInteger(hostId)) { - return res - .status(400) - .json({ error: "Valid fleetId and hostId are required" }); - } - - try { - const fleetRepository = createCurrentFleetRepository(); - const fleet = await fleetRepository.findById(userId, fleetId); - if (!fleet) { - return res.status(404).json({ error: "Fleet not found" }); - } - - const access = await permissionManager.canAccessHost( - userId, - hostId, - "connect", - ); - if (!access.hasAccess) { - return res.status(404).json({ error: "Host not found" }); - } - - await fleetRepository.addMember(fleetId, hostId); - res.json({ success: true }); - } catch (err) { - databaseLogger.error("Failed to add fleet member", err, { - operation: "fleet_member_add_failed", - userId, - fleetId, - hostId, - }); - res.status(500).json({ error: "Failed to add fleet member" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}/members/{hostId}: - * delete: - * summary: Remove a host from a fleet's static membership - * description: Only removes the static membership row - a host still matched by the fleet's tag rules remains an effective member. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * - in: path - * name: hostId - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Host removed. - * 404: - * description: Fleet or membership not found. - */ -router.delete( - "/:id/members/:hostId", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - const hostId = parseFleetId(req.params.hostId); - - if (fleetId === null || hostId === null) { - return res.status(400).json({ error: "Invalid fleet or host ID" }); - } - - try { - const fleetRepository = createCurrentFleetRepository(); - const fleet = await fleetRepository.findById(userId, fleetId); - if (!fleet) { - return res.status(404).json({ error: "Fleet not found" }); - } - - const removed = await fleetRepository.removeMember(fleetId, hostId); - if (!removed) { - return res.status(404).json({ error: "Membership not found" }); - } - res.json({ success: true }); - } catch (err) { - databaseLogger.error("Failed to remove fleet member", err, { - operation: "fleet_member_remove_failed", - userId, - fleetId, - hostId, - }); - res.status(500).json({ error: "Failed to remove fleet member" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}/share: - * post: - * summary: Share a fleet's current member hosts with users or roles - * description: Snapshot at share time - grants hostAccess for every current member host to each target. Hosts added to the fleet later are not automatically shared; re-run this route to extend sharing to new members. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * targets: - * type: array - * items: - * type: object - * permissionLevel: - * type: string - * durationHours: - * type: number - * responses: - * 200: - * description: Fleet shared. - * 404: - * description: Fleet not found. - */ -router.post( - "/:id/share", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - - try { - const fleetRepository = createCurrentFleetRepository(); - const fleet = await fleetRepository.findById(userId, fleetId); - if (!fleet) { - return res.status(404).json({ error: "Fleet not found" }); - } - - const targets = parseShareTargets(req.body ?? {}); - if (!targets) { - return res.status(400).json({ - error: - "targets must be a non-empty array of { type: 'user'|'role', id } entries", - }); - } - - const { durationHours, permissionLevel = "connect" } = req.body; - - if (!isSharePermissionLevel(permissionLevel)) { - return res.status(400).json({ error: "Invalid permission level" }); - } - - const userRepository = createCurrentUserRepository(); - const roleRepository = createCurrentRoleRepository(); - for (const target of targets) { - if (target.type === "user") { - const targetUser = await userRepository.findById(target.id as string); - if (!targetUser) { - return res - .status(404) - .json({ error: "Target user not found", targetId: target.id }); - } - } else { - const targetRole = await roleRepository.findRoleById( - target.id as number, - ); - if (!targetRole) { - return res - .status(404) - .json({ error: "Target role not found", targetId: target.id }); - } - } - } - - const members = await fleetRepository.listEffectiveMembers( - userId, - fleetId, - ); - const expiresAt = expiryFromDuration(durationHours); - const rbacAccessRepository = createCurrentRbacAccessRepository(); - const { SharedHostSecretsManager } = - await import("../../../../src/backend/utils/shared-host-secrets-manager.js"); - const secretsManager = SharedHostSecretsManager.getInstance(); - - const hostResults: Array<{ - hostId: number; - shared: boolean; - reason?: string; - }> = []; - - for (const host of members) { - if (targets.some((t) => t.type === "user" && t.id === host.userId)) { - hostResults.push({ hostId: host.id, shared: false, reason: "owner" }); - continue; - } - - const access = await permissionManager.canAccessHost( - userId, - host.id, - "manage", - ); - if (!access.hasAccess) { - hostResults.push({ - hostId: host.id, - shared: false, - reason: "forbidden", - }); - continue; - } - - for (const target of targets) { - const accessGrant = await rbacAccessRepository.upsertHostAccess({ - hostId: host.id, - grantedBy: userId, - permissionLevel, - expiresAt, - ...(target.type === "user" - ? { - targetType: "user" as const, - targetUserId: target.id as string, - } - : { - targetType: "role" as const, - targetRoleId: target.id as number, - }), - }); - - try { - if (target.type === "user") { - await secretsManager.snapshotForUser( - accessGrant.id, - host.id, - target.id as string, - host.userId, - ); - } else { - await secretsManager.snapshotForRole( - accessGrant.id, - host.id, - target.id as number, - host.userId, - ); - } - } catch (snapshotError) { - databaseLogger.warn("Fleet shared but secret snapshot failed", { - operation: "fleet_share_snapshot_failed", - hostId: host.id, - accessId: accessGrant.id, - error: getErrorMessage(snapshotError), - }); - } - } - - hostResults.push({ hostId: host.id, shared: true }); - } - - const sharedCount = hostResults.filter((r) => r.shared).length; - - databaseLogger.success("Fleet shared successfully", { - operation: "fleet_share_success", - userId, - fleetId, - hostsShared: sharedCount, - targets: targets.length, - permissionLevel, - }); - - res.json({ - success: true, - permissionLevel, - expiresAt, - hostsShared: sharedCount, - hostsTotal: members.length, - hostResults, - }); - } catch (err) { - databaseLogger.error("Failed to share fleet", err, { - operation: "fleet_share_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to share fleet" }); - } - }, -); - -// Minimal promisified SFTP primitives for whole-file push/pull. Deliberately -// not reusing transfer-engine.ts - that file's resumable/segmented/tar -// machinery is built for the interactive file manager's large-transfer UX -// and is overbuilt for a fleet action operating on one buffered file per host. function getSftp(client: Client): Promise { return new Promise((resolve, reject) => { client.sftp((err, sftp) => { @@ -770,391 +130,6 @@ interface FleetHostResult { error?: string; } -/** - * Resolves a fleet's effective members, re-checks the caller's per-host - * access at `level` (fleet membership alone is not treated as authorization - - * a fleet-sharee can only act on member hosts they individually have access - * to), and runs `fn` against each authorized host concurrently. One host's - * rejection never aborts the others. - */ -async function runAcrossFleet( - userId: string, - fleetId: number, - level: HostAction, - fn: (host: { - id: number; - name: string; - }) => Promise>, -): Promise<{ results: FleetHostResult[]; fleetFound: boolean }> { - const fleetRepository = createCurrentFleetRepository(); - const fleet = await fleetRepository.findById(userId, fleetId); - if (!fleet) { - return { results: [], fleetFound: false }; - } - - const members = await fleetRepository.listEffectiveMembers(userId, fleetId); - - const settled = await Promise.allSettled( - members.map(async (host) => { - const access = await permissionManager.canAccessHost( - userId, - host.id, - level, - ); - if (!access.hasAccess) { - return { - hostId: host.id, - hostName: host.name, - success: false, - error: `Access denied (requires '${level}' level)`, - } satisfies FleetHostResult; - } - - try { - const outcome = await fn({ id: host.id, name: host.name }); - return { - hostId: host.id, - hostName: host.name, - ...outcome, - } satisfies FleetHostResult; - } catch (err) { - return { - hostId: host.id, - hostName: host.name, - success: false, - error: getErrorMessage(err), - } satisfies FleetHostResult; - } - }), - ); - - const results = settled.map((r) => - r.status === "fulfilled" - ? r.value - : ({ - hostId: -1, - hostName: "unknown", - success: false, - error: r.reason instanceof Error ? r.reason.message : "Unknown error", - } satisfies FleetHostResult), - ); - - return { results, fleetFound: true }; -} - -/** - * @openapi - * /fleets/{id}/execute: - * post: - * summary: Run a command across every host in a fleet - * description: Fans out concurrently to every effective member host the caller has edit-level access to. $HOST/$USER/$PORT/$NAME/$INPUT_n substitution is applied per host, same grammar as snippet execution. One host failing does not stop the others. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * command: - * type: string - * inputValues: - * type: object - * responses: - * 200: - * description: Per-host execution results. - * 404: - * description: Fleet not found. - */ -router.post( - "/:id/execute", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - const { command, inputValues } = req.body ?? {}; - - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - if (!isNonEmptyString(command)) { - return res.status(400).json({ error: "Command is required" }); - } - - try { - const { results, fleetFound } = await runAcrossFleet( - userId, - fleetId, - "edit", - async (host) => { - const fullHost = await resolveHostById(host.id, userId); - if (!fullHost) { - return { success: false, error: "Host not found" }; - } - - const resolvedCommand = resolveCommandVariables( - command, - { - ip: fullHost.ip, - username: fullHost.username, - port: fullHost.port, - name: fullHost.name, - }, - inputValues && typeof inputValues === "object" ? inputValues : {}, - ); - - const { stdout, stderr, code } = await withSshConnection( - fullHost as never, - { pool: "fleet", purpose: "fleet" }, - (client) => execCommand(client, resolvedCommand, 60000), - ); - - return { - success: code === 0, - output: stdout, - error: - code !== 0 ? stderr || `Exited with code ${code}` : undefined, - }; - }, - ); - - if (!fleetFound) { - return res.status(404).json({ error: "Fleet not found" }); - } - - authLogger.success(`Fleet command executed on fleet ${fleetId}`, { - operation: "fleet_execute_success", - userId, - fleetId, - hostCount: results.length, - }); - - res.json({ results }); - } catch (err) { - databaseLogger.error("Failed to execute fleet command", err, { - operation: "fleet_execute_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to execute fleet command" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}/transfer/push: - * post: - * summary: Push an uploaded file to the same remote path on every host in a fleet - * description: Fans out concurrently to every effective member host the caller has edit-level access to. Single file only (v1) - the file is buffered once server-side and written to each host via SFTP. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * remotePath: - * type: string - * responses: - * 200: - * description: Per-host push results. - * 404: - * description: Fleet not found. - */ -router.post( - "/:id/transfer/push", - authenticateJWT, - requireDataAccess, - upload.single("file"), - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - const remotePath = req.body?.remotePath; - const file = (req as Request & { file?: Express.Multer.File }).file; - - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - if (!isNonEmptyString(remotePath)) { - return res.status(400).json({ error: "remotePath is required" }); - } - if (!file) { - return res.status(400).json({ error: "A file is required" }); - } - - try { - const { results, fleetFound } = await runAcrossFleet( - userId, - fleetId, - "edit", - async (host) => { - const fullHost = await resolveHostById(host.id, userId); - if (!fullHost) { - return { success: false, error: "Host not found" }; - } - - await withSshConnection( - fullHost as never, - { pool: "fleet", purpose: "fleet" }, - async (client) => { - const sftp = await getSftp(client); - await sftpWriteFile(sftp, remotePath, file.buffer); - }, - ); - - return { success: true, output: `Written to ${remotePath}` }; - }, - ); - - if (!fleetFound) { - return res.status(404).json({ error: "Fleet not found" }); - } - - authLogger.success(`Fleet file push executed on fleet ${fleetId}`, { - operation: "fleet_transfer_push_success", - userId, - fleetId, - hostCount: results.length, - }); - - res.json({ results }); - } catch (err) { - databaseLogger.error("Failed to push file across fleet", err, { - operation: "fleet_transfer_push_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to push file across fleet" }); - } - }, -); - -/** - * @openapi - * /fleets/{id}/transfer/pull: - * post: - * summary: Pull the same remote path from every host in a fleet - * description: Fans out concurrently to every effective member host the caller has edit-level access to, reads remotePath via SFTP from each, and returns a single zip archive with one entry per successful host (/). Single file only (v1). - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * remotePath: - * type: string - * responses: - * 200: - * description: Zip archive containing the per-host pulled files, plus an X-Fleet-Transfer-Results header with the per-host JSON result summary. - * 404: - * description: Fleet not found. - */ -router.post( - "/:id/transfer/pull", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - const { remotePath } = req.body ?? {}; - - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - if (!isNonEmptyString(remotePath)) { - return res.status(400).json({ error: "remotePath is required" }); - } - - try { - const zip = new JSZip(); - const fileName = remotePath.split("/").filter(Boolean).pop() || "file"; - - const { results, fleetFound } = await runAcrossFleet( - userId, - fleetId, - "edit", - async (host) => { - const fullHost = await resolveHostById(host.id, userId); - if (!fullHost) { - return { success: false, error: "Host not found" }; - } - - const data = await withSshConnection( - fullHost as never, - { pool: "fleet", purpose: "fleet" }, - async (client) => { - const sftp = await getSftp(client); - return sftpReadFile(sftp, remotePath); - }, - ); - - zip.file(`${host.name}/${fileName}`, data); - return { success: true, output: `Pulled ${data.length} bytes` }; - }, - ); - - if (!fleetFound) { - return res.status(404).json({ error: "Fleet not found" }); - } - - authLogger.success(`Fleet file pull executed on fleet ${fleetId}`, { - operation: "fleet_transfer_pull_success", - userId, - fleetId, - hostCount: results.length, - }); - - const archive = await zip.generateAsync({ type: "nodebuffer" }); - res.setHeader("Content-Type", "application/zip"); - res.setHeader( - "Content-Disposition", - `attachment; filename="fleet-${fleetId}-${fileName}.zip"`, - ); - res.setHeader( - "X-Fleet-Transfer-Results", - Buffer.from(JSON.stringify(results)).toString("base64"), - ); - res.send(archive); - } catch (err) { - databaseLogger.error("Failed to pull file across fleet", err, { - operation: "fleet_transfer_pull_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to pull file across fleet" }); - } - }, -); - // Kernel/arch/hostname/uptime are cheap, always-available facts not covered by // detectPlatform's tooling probe. One combined command keeps this to a single // round trip per host, same "key=value per line" shape as PLATFORM_PROBE_COMMAND. @@ -1191,239 +166,1180 @@ export function parseInventoryProbe(output: string): { } /** - * @openapi - * /fleets/{id}/inventory: - * get: - * summary: Read the last-known inventory snapshot for a fleet's members - * description: No live connection - reads back whatever the most recent POST refresh stored. Latest-only per host, no history. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Stored inventory rows for current members. - * 404: - * description: Fleet not found. + * Resolves a fleet's effective members, re-checks the caller's per-host + * access at `level` through ctx.hosts (fleet membership alone is not treated + * as authorization - a fleet-sharee can only act on member hosts they + * individually have access to), and runs `fn` against each authorized host + * concurrently. One host's rejection never aborts the others. */ -router.get( - "/:id/inventory", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } +async function runAcrossFleet( + ctx: PluginContext, + repo: FleetRepository, + userId: string, + fleetId: number, + level: PluginHostShareLevel, + fn: (host: { + id: number; + name: string; + }) => Promise>, +): Promise<{ results: FleetHostResult[]; fleetFound: boolean }> { + const fleet = await repo.findById(userId, fleetId); + if (!fleet) { + return { results: [], fleetFound: false }; + } - try { - const fleetRepository = createCurrentFleetRepository(); - const fleet = await fleetRepository.findById(userId, fleetId); - if (!fleet) { - return res.status(404).json({ error: "Fleet not found" }); + const members = await repo.listEffectiveMembers(userId, fleetId); + + const settled = await Promise.allSettled( + members.map(async (host) => { + const access = await ctx.hosts.checkAccess(host.id, level); + if (!access.hasAccess) { + return { + hostId: host.id, + hostName: host.name ?? String(host.id), + success: false, + error: `Access denied (requires '${level}' level)`, + } satisfies FleetHostResult; } - const members = await fleetRepository.listEffectiveMembers( - userId, - fleetId, - ); - const inventory = - await createCurrentFleetInventoryRepository().listForHosts( + try { + const outcome = await fn({ + id: host.id, + name: host.name ?? String(host.id), + }); + return { + hostId: host.id, + hostName: host.name ?? String(host.id), + ...outcome, + } satisfies FleetHostResult; + } catch (err) { + return { + hostId: host.id, + hostName: host.name ?? String(host.id), + success: false, + error: err instanceof Error ? err.message : "Unknown error", + } satisfies FleetHostResult; + } + }), + ); + + const results = settled.map((r) => + r.status === "fulfilled" + ? r.value + : ({ + hostId: -1, + hostName: "unknown", + success: false, + error: r.reason instanceof Error ? r.reason.message : "Unknown error", + } satisfies FleetHostResult), + ); + + return { results, fleetFound: true }; +} + +/** + * Mounts the fleets routes on the plugin's router, which core serves at + * /plugin-api/fleets/ with auth and the actor already applied. + */ +export function registerFleetRoutes( + router: Router, + repo: FleetRepository, + ctx: PluginContext, +): void { + /** + * @openapi + * /plugin-api/fleets: + * get: + * summary: List the current user's fleets + * tags: + * - Fleets + * responses: + * 200: + * description: List of fleets with member counts. + */ + router.get( + "/", + ctx.rbac.require("view") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + try { + const fleetList = await repo.listByUser(userId); + const withCounts = await Promise.all( + fleetList.map(async (fleet) => { + const members = await repo.listEffectiveMembers(userId, fleet.id); + return { + ...fleet, + tagRules: fleet.tagRules ? JSON.parse(fleet.tagRules) : [], + memberCount: members.length, + }; + }), + ); + res.json(withCounts); + } catch (err) { + logError(ctx, "Failed to list fleets", err, "fleet_list_failed"); + res.status(500).json({ error: "Failed to list fleets" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets: + * post: + * summary: Create a fleet + * tags: + * - Fleets + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: + * type: string + * color: + * type: string + * icon: + * type: string + * tagRules: + * type: array + * items: + * type: string + * responses: + * 200: + * description: Fleet created. + * 400: + * description: Invalid request body. + */ + router.post( + "/", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const { name, description, color, icon, tagRules } = req.body ?? {}; + + if (!isNonEmptyString(name)) { + return res.status(400).json({ error: "Fleet name is required" }); + } + if ( + tagRules !== undefined && + (!Array.isArray(tagRules) || + tagRules.some((t) => typeof t !== "string")) + ) { + return res + .status(400) + .json({ error: "tagRules must be an array of strings" }); + } + + try { + const fleet = await repo.create(userId, { + name: name.trim(), + description: isNonEmptyString(description) + ? description.trim() + : null, + color: isNonEmptyString(color) ? color : null, + icon: isNonEmptyString(icon) ? icon : null, + tagRules, + }); + res.json({ ...fleet, tagRules: tagRules ?? [] }); + } catch (err) { + logError(ctx, "Failed to create fleet", err, "fleet_create_failed"); + res.status(500).json({ error: "Failed to create fleet" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}: + * patch: + * summary: Update a fleet + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Fleet updated. + * 404: + * description: Fleet not found. + */ + router.patch( + "/:id", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + + const { name, description, color, icon, tagRules } = req.body ?? {}; + if (name !== undefined && !isNonEmptyString(name)) { + return res.status(400).json({ error: "Fleet name cannot be empty" }); + } + if ( + tagRules !== undefined && + (!Array.isArray(tagRules) || + tagRules.some((t) => typeof t !== "string")) + ) { + return res + .status(400) + .json({ error: "tagRules must be an array of strings" }); + } + + try { + const updated = await repo.update(userId, fleetId, { + name: name !== undefined ? name.trim() : undefined, + description, + color, + icon, + tagRules, + }); + if (!updated) { + return res.status(404).json({ error: "Fleet not found" }); + } + res.json({ + ...updated, + tagRules: updated.tagRules ? JSON.parse(updated.tagRules) : [], + }); + } catch (err) { + logError(ctx, "Failed to update fleet", err, "fleet_update_failed"); + res.status(500).json({ error: "Failed to update fleet" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}: + * delete: + * summary: Delete a fleet + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Fleet deleted. + * 404: + * description: Fleet not found. + */ + router.delete( + "/:id", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + try { + const deleted = await repo.delete(userId, fleetId); + if (!deleted) { + return res.status(404).json({ error: "Fleet not found" }); + } + res.json({ success: true }); + } catch (err) { + logError(ctx, "Failed to delete fleet", err, "fleet_delete_failed"); + res.status(500).json({ error: "Failed to delete fleet" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/members: + * get: + * summary: List the resolved effective members of a fleet + * description: Returns the union of statically-added hosts and hosts matched by the fleet's tag rules, each annotated with the caller's permission level on that host. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Resolved member hosts. + * 404: + * description: Fleet not found. + */ + router.get( + "/:id/members", + ctx.rbac.require("view") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + + try { + const fleet = await repo.findById(userId, fleetId); + if (!fleet) { + return res.status(404).json({ error: "Fleet not found" }); + } + + const staticIds = await repo.listStaticMemberIds(fleetId); + const staticIdSet = new Set(staticIds); + const members = await repo.listEffectiveMembers(userId, fleetId); + + const annotated = await Promise.all( + members.map(async (host) => { + const access = await ctx.hosts.checkAccess(host.id, "connect"); + return { + id: host.id, + name: host.name, + ip: host.ip, + tags: host.tags ? host.tags.split(",").filter(Boolean) : [], + static: staticIdSet.has(host.id), + permissionLevel: access.isOwner + ? "manage" + : (access.permissionLevel ?? null), + }; + }), + ); + + res.json(annotated); + } catch (err) { + logError( + ctx, + "Failed to list fleet members", + err, + "fleet_members_list_failed", + ); + res.status(500).json({ error: "Failed to list fleet members" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/members: + * post: + * summary: Add a host to a fleet's static membership + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * hostId: + * type: number + * responses: + * 200: + * description: Host added. + * 404: + * description: Fleet or host not found. + */ + router.post( + "/:id/members", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + const hostId = Number(req.body?.hostId); + + if (fleetId === null || !Number.isInteger(hostId)) { + return res + .status(400) + .json({ error: "Valid fleetId and hostId are required" }); + } + + try { + const fleet = await repo.findById(userId, fleetId); + if (!fleet) { + return res.status(404).json({ error: "Fleet not found" }); + } + + const access = await ctx.hosts.checkAccess(hostId, "connect"); + if (!access.hasAccess) { + return res.status(404).json({ error: "Host not found" }); + } + + await repo.addMember(fleetId, hostId); + res.json({ success: true }); + } catch (err) { + logError( + ctx, + "Failed to add fleet member", + err, + "fleet_member_add_failed", + ); + res.status(500).json({ error: "Failed to add fleet member" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/members/{hostId}: + * delete: + * summary: Remove a host from a fleet's static membership + * description: Only removes the static membership row - a host still matched by the fleet's tag rules remains an effective member. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * - in: path + * name: hostId + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Host removed. + * 404: + * description: Fleet or membership not found. + */ + router.delete( + "/:id/members/:hostId", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + const hostId = parseFleetId(req.params.hostId); + + if (fleetId === null || hostId === null) { + return res.status(400).json({ error: "Invalid fleet or host ID" }); + } + + try { + const fleet = await repo.findById(userId, fleetId); + if (!fleet) { + return res.status(404).json({ error: "Fleet not found" }); + } + + const removed = await repo.removeMember(fleetId, hostId); + if (!removed) { + return res.status(404).json({ error: "Membership not found" }); + } + res.json({ success: true }); + } catch (err) { + logError( + ctx, + "Failed to remove fleet member", + err, + "fleet_member_remove_failed", + ); + res.status(500).json({ error: "Failed to remove fleet member" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/share: + * post: + * summary: Share a fleet's current member hosts with users or roles + * description: Snapshot at share time - grants host access for every current member host to each target. Hosts added to the fleet later are not automatically shared; re-run this route to extend sharing to new members. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * targets: + * type: array + * items: + * type: object + * permissionLevel: + * type: string + * durationHours: + * type: number + * responses: + * 200: + * description: Fleet shared. + * 404: + * description: Fleet not found. + */ + router.post( + "/:id/share", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + + try { + const fleet = await repo.findById(userId, fleetId); + if (!fleet) { + return res.status(404).json({ error: "Fleet not found" }); + } + + const targets = parseShareTargets(req.body ?? {}); + if (!targets) { + return res.status(400).json({ + error: + "targets must be a non-empty array of { type: 'user'|'role', id } entries", + }); + } + + const { durationHours, permissionLevel = "connect" } = req.body; + if (!isShareLevel(permissionLevel)) { + return res.status(400).json({ error: "Invalid permission level" }); + } + + const members = await repo.listEffectiveMembers(userId, fleetId); + const hostResults: Array<{ + hostId: number; + shared: boolean; + reason?: string; + }> = []; + + for (const host of members) { + if (targets.some((t) => t.type === "user" && t.id === host.userId)) { + hostResults.push({ + hostId: host.id, + shared: false, + reason: "owner", + }); + continue; + } + const result = await ctx.hosts.share( + host.id, + targets, + permissionLevel, + typeof durationHours === "number" ? durationHours : undefined, + ); + hostResults.push(result); + } + + const sharedCount = hostResults.filter((r) => r.shared).length; + const expiresAt = + typeof durationHours === "number" && durationHours > 0 + ? new Date( + Date.now() + durationHours * 60 * 60 * 1000, + ).toISOString() + : null; + + res.json({ + success: true, + permissionLevel, + expiresAt, + hostsShared: sharedCount, + hostsTotal: members.length, + hostResults, + }); + } catch (err) { + logError(ctx, "Failed to share fleet", err, "fleet_share_failed"); + res.status(500).json({ error: "Failed to share fleet" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/share-targets/users: + * get: + * summary: Users the caller may pick as a fleet share target + * tags: + * - Fleets + * responses: + * 200: + * description: Users list. + */ + router.get( + "/share-targets/users", + ctx.rbac.require("manage") as never, + async (_req: Request, res: Response) => { + try { + res.json({ users: await ctx.hosts.listUsers() }); + } catch (err) { + logError( + ctx, + "Failed to list share target users", + err, + "fleet_share_targets_users_failed", + ); + res.status(500).json({ error: "Failed to list users" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/share-targets/roles: + * get: + * summary: Non-system roles the caller may pick as a fleet share target + * tags: + * - Fleets + * responses: + * 200: + * description: Roles list. + */ + router.get( + "/share-targets/roles", + ctx.rbac.require("manage") as never, + async (_req: Request, res: Response) => { + try { + res.json({ roles: await ctx.hosts.listRoles() }); + } catch (err) { + logError( + ctx, + "Failed to list share target roles", + err, + "fleet_share_targets_roles_failed", + ); + res.status(500).json({ error: "Failed to list roles" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/execute: + * post: + * summary: Run a command across every host in a fleet + * description: Fans out concurrently to every effective member host the caller has edit-level access to. $HOST/$USER/$PORT/$NAME/$INPUT_n substitution is applied per host, same grammar as snippet execution. One host failing does not stop the others. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * command: + * type: string + * inputValues: + * type: object + * responses: + * 200: + * description: Per-host execution results. + * 404: + * description: Fleet not found. + */ + router.post( + "/:id/execute", + ctx.rbac.require("execute") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + const { command, inputValues } = req.body ?? {}; + + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + if (!isNonEmptyString(command)) { + return res.status(400).json({ error: "Command is required" }); + } + + try { + const { results, fleetFound } = await runAcrossFleet( + ctx, + repo, + userId, + fleetId, + "edit", + async (host) => { + const fullHost = await ctx.hosts.get(host.id); + if (!fullHost) { + return { success: false, error: "Host not found" }; + } + + const resolvedCommand = resolveCommandVariables( + command, + { + ip: fullHost.ip, + username: fullHost.username, + port: fullHost.port, + name: fullHost.name ?? undefined, + }, + inputValues && typeof inputValues === "object" ? inputValues : {}, + ); + + const { stdout, stderr, code } = await ctx.ssh.withConnection( + host.id, + { pool: "fleet", purpose: "fleet" }, + (client) => execCommand(client as Client, resolvedCommand, 60000), + ); + + return { + success: code === 0, + output: stdout, + error: + code !== 0 ? stderr || `Exited with code ${code}` : undefined, + }; + }, + ); + + if (!fleetFound) { + return res.status(404).json({ error: "Fleet not found" }); + } + + res.json({ results }); + } catch (err) { + logError( + ctx, + "Failed to execute fleet command", + err, + "fleet_execute_failed", + ); + res.status(500).json({ error: "Failed to execute fleet command" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/transfer/push: + * post: + * summary: Push an uploaded file to the same remote path on every host in a fleet + * description: Fans out concurrently to every effective member host the caller has edit-level access to. Single file only (v1) - the file is buffered once server-side and written to each host via SFTP. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * file: + * type: string + * format: binary + * remotePath: + * type: string + * responses: + * 200: + * description: Per-host push results. + * 404: + * description: Fleet not found. + */ + router.post( + "/:id/transfer/push", + ctx.rbac.require("execute") as never, + upload.single("file"), + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + const remotePath = req.body?.remotePath; + const file = (req as Request & { file?: Express.Multer.File }).file; + + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + if (!isNonEmptyString(remotePath)) { + return res.status(400).json({ error: "remotePath is required" }); + } + if (!file) { + return res.status(400).json({ error: "A file is required" }); + } + + try { + const { results, fleetFound } = await runAcrossFleet( + ctx, + repo, + userId, + fleetId, + "edit", + async (host) => { + await ctx.ssh.withConnection( + host.id, + { pool: "fleet", purpose: "fleet" }, + async (client) => { + const sftp = await getSftp(client as Client); + await sftpWriteFile(sftp, remotePath, file.buffer); + }, + ); + + return { success: true, output: `Written to ${remotePath}` }; + }, + ); + + if (!fleetFound) { + return res.status(404).json({ error: "Fleet not found" }); + } + + res.json({ results }); + } catch (err) { + logError( + ctx, + "Failed to push file across fleet", + err, + "fleet_transfer_push_failed", + ); + res.status(500).json({ error: "Failed to push file across fleet" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/transfer/pull: + * post: + * summary: Pull the same remote path from every host in a fleet + * description: Fans out concurrently to every effective member host the caller has edit-level access to, reads remotePath via SFTP from each, and returns a single zip archive with one entry per successful host (/). Single file only (v1). + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * remotePath: + * type: string + * responses: + * 200: + * description: Zip archive containing the per-host pulled files, plus an X-Fleet-Transfer-Results header with the per-host JSON result summary. + * 404: + * description: Fleet not found. + */ + router.post( + "/:id/transfer/pull", + ctx.rbac.require("execute") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + const { remotePath } = req.body ?? {}; + + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + if (!isNonEmptyString(remotePath)) { + return res.status(400).json({ error: "remotePath is required" }); + } + + try { + const zip = new JSZip(); + const fileName = remotePath.split("/").filter(Boolean).pop() || "file"; + + const { results, fleetFound } = await runAcrossFleet( + ctx, + repo, + userId, + fleetId, + "edit", + async (host) => { + const data = await ctx.ssh.withConnection( + host.id, + { pool: "fleet", purpose: "fleet" }, + async (client) => { + const sftp = await getSftp(client as Client); + return sftpReadFile(sftp, remotePath); + }, + ); + + zip.file(`${host.name}/${fileName}`, data); + return { success: true, output: `Pulled ${data.length} bytes` }; + }, + ); + + if (!fleetFound) { + return res.status(404).json({ error: "Fleet not found" }); + } + + const archive = await zip.generateAsync({ type: "nodebuffer" }); + res.setHeader("Content-Type", "application/zip"); + res.setHeader( + "Content-Disposition", + `attachment; filename="fleet-${fleetId}-${fileName}.zip"`, + ); + res.setHeader( + "X-Fleet-Transfer-Results", + Buffer.from(JSON.stringify(results)).toString("base64"), + ); + res.send(archive); + } catch (err) { + logError( + ctx, + "Failed to pull file across fleet", + err, + "fleet_transfer_pull_failed", + ); + res.status(500).json({ error: "Failed to pull file across fleet" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/inventory: + * get: + * summary: Read the last-known inventory snapshot for a fleet's members + * description: No live connection - reads back whatever the most recent POST refresh stored. Latest-only per host, no history. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Stored inventory rows for current members. + * 404: + * description: Fleet not found. + */ + router.get( + "/:id/inventory", + ctx.rbac.require("view") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + + try { + const fleet = await repo.findById(userId, fleetId); + if (!fleet) { + return res.status(404).json({ error: "Fleet not found" }); + } + + const members = await repo.listEffectiveMembers(userId, fleetId); + const inventory = await repo.listInventoryForHosts( userId, members.map((m) => m.id), ); - const byHostId = new Map(inventory.map((row) => [row.hostId, row])); - res.json( - members.map((host) => ({ - hostId: host.id, - hostName: host.name, - inventory: byHostId.get(host.id) ?? null, - })), - ); - } catch (err) { - databaseLogger.error("Failed to read fleet inventory", err, { - operation: "fleet_inventory_read_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to read fleet inventory" }); - } - }, -); + const byHostId = new Map(inventory.map((row) => [row.hostId, row])); + res.json( + members.map((host) => ({ + hostId: host.id, + hostName: host.name, + inventory: byHostId.get(host.id) ?? null, + })), + ); + } catch (err) { + logError( + ctx, + "Failed to read fleet inventory", + err, + "fleet_inventory_read_failed", + ); + res.status(500).json({ error: "Failed to read fleet inventory" }); + } + }, + ); -/** - * @openapi - * /fleets/{id}/inventory: - * post: - * summary: Refresh the inventory snapshot for every host in a fleet - * description: Connects to every effective member host the caller has view-level access to, collects OS/kernel/arch/hostname/uptime, and overwrites the stored latest-only snapshot per host. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Per-host refresh results. - * 404: - * description: Fleet not found. - */ -router.post( - "/:id/inventory", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - - try { - const inventoryRepository = createCurrentFleetInventoryRepository(); - - const { results, fleetFound } = await runAcrossFleet( - userId, - fleetId, - "view", - async (host) => { - const fullHost = await resolveHostById(host.id, userId); - if (!fullHost) { - return { success: false, error: "Host not found" }; - } - - const record = await withSshConnection( - fullHost as never, - { pool: "fleet", purpose: "fleet" }, - async (client) => { - const platform = await detectPlatform(client); - const { stdout } = await execCommand( - client, - INVENTORY_PROBE_COMMAND, - 15000, - ); - const facts = parseInventoryProbe(stdout); - - return inventoryRepository.upsert(userId, host.id, { - osPrettyName: platform.osPrettyName, - kernel: facts.kernel, - architecture: facts.architecture, - hostname: facts.hostname, - uptimeSeconds: facts.uptimeSeconds, - ip: fullHost.ip, - packageManager: platform.pkg, - }); - }, - ); - - return { - success: true, - output: JSON.stringify(record), - }; - }, - ); - - if (!fleetFound) { - return res.status(404).json({ error: "Fleet not found" }); + /** + * @openapi + * /plugin-api/fleets/{id}/inventory: + * post: + * summary: Refresh the inventory snapshot for every host in a fleet + * description: Connects to every effective member host the caller has view-level access to, collects OS/kernel/arch/hostname/uptime, and overwrites the stored latest-only snapshot per host. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Per-host refresh results. + * 404: + * description: Fleet not found. + */ + router.post( + "/:id/inventory", + ctx.rbac.require("execute") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); } - res.json({ results }); - } catch (err) { - databaseLogger.error("Failed to refresh fleet inventory", err, { - operation: "fleet_inventory_refresh_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to refresh fleet inventory" }); - } - }, -); + try { + const { results, fleetFound } = await runAcrossFleet( + ctx, + repo, + userId, + fleetId, + "view", + async (host) => { + const fullHost = await ctx.hosts.get(host.id); + if (!fullHost) { + return { success: false, error: "Host not found" }; + } -/** - * @openapi - * /fleets/{id}/packages: - * post: - * summary: Run a package action across every host in a fleet - * description: Auto-detects each host's package manager (apt/dnf/yum/pacman) and runs install/remove/upgrade-all, elevating with the host's stored sudo password. Requires manage-level access per host. - * tags: - * - Fleets - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * action: - * type: string - * enum: [install, remove, upgrade-all] - * package: - * type: string - * responses: - * 200: - * description: Per-host package action results. - * 404: - * description: Fleet not found. - */ -router.post( - "/:id/packages", - authenticateJWT, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const fleetId = parseFleetId(req.params.id); - const { action, package: packageName } = req.body ?? {}; + const record = await ctx.ssh.withConnection( + host.id, + { pool: "fleet", purpose: "fleet" }, + async (client) => { + const sshClient = client as Client; + const platform = await detectPlatform(sshClient); + const { stdout } = await execCommand( + sshClient, + INVENTORY_PROBE_COMMAND, + 15000, + ); + const facts = parseInventoryProbe(stdout); - if (fleetId === null) { - return res.status(400).json({ error: "Invalid fleet ID" }); - } - if ( - action !== "install" && - action !== "remove" && - action !== "upgrade-all" - ) { - return res.status(400).json({ error: "Invalid action" }); - } - if (action !== "upgrade-all" && !isValidPackageName(packageName)) { - return res.status(400).json({ error: "Invalid package name" }); - } + return repo.upsertInventory(userId, host.id, { + osPrettyName: platform.osPrettyName, + kernel: facts.kernel, + architecture: facts.architecture, + hostname: facts.hostname, + uptimeSeconds: facts.uptimeSeconds, + ip: fullHost.ip, + packageManager: platform.pkg, + }); + }, + ); - try { - const { results, fleetFound } = await runAcrossFleet( - userId, - fleetId, - "manage", - async (host) => { - const fullHost = await resolveHostById(host.id, userId); - if (!fullHost) { - return { success: false, error: "Host not found" }; - } + return { + success: true, + output: JSON.stringify(record), + }; + }, + ); - return withSshConnection( - fullHost as never, - { pool: "fleet", purpose: "fleet" }, - async (client) => { - const platform = await detectPlatform(client); - // "remove" has no PackageAction counterpart in buildPackageActionCommand - // (install/upgrade-all only) - build it here per distro instead. + if (!fleetFound) { + return res.status(404).json({ error: "Fleet not found" }); + } + + res.json({ results }); + } catch (err) { + logError( + ctx, + "Failed to refresh fleet inventory", + err, + "fleet_inventory_refresh_failed", + ); + res.status(500).json({ error: "Failed to refresh fleet inventory" }); + } + }, + ); + + /** + * @openapi + * /plugin-api/fleets/{id}/packages: + * post: + * summary: Run a package action across every host in a fleet + * description: Auto-detects each host's package manager (apt/dnf/yum/pacman) and runs install/remove/upgrade-all, elevating with the host's stored sudo password. Requires manage-level access per host. + * tags: + * - Fleets + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * action: + * type: string + * enum: [install, remove, upgrade-all] + * package: + * type: string + * responses: + * 200: + * description: Per-host package action results. + * 404: + * description: Fleet not found. + */ + router.post( + "/:id/packages", + ctx.rbac.require("manage") as never, + async (req: Request, res: Response) => { + const userId = actor(ctx); + const fleetId = parseFleetId(req.params.id); + const { action, package: packageName } = req.body ?? {}; + + if (fleetId === null) { + return res.status(400).json({ error: "Invalid fleet ID" }); + } + if ( + action !== "install" && + action !== "remove" && + action !== "upgrade-all" + ) { + return res.status(400).json({ error: "Invalid action" }); + } + if (action !== "upgrade-all" && !isValidPackageName(packageName)) { + return res.status(400).json({ error: "Invalid package name" }); + } + + try { + const { results, fleetFound } = await runAcrossFleet( + ctx, + repo, + userId, + fleetId, + "manage", + async (host) => { + // Package actions need the host's sudo password, an operational + // secret ctx.hosts.get() leaves out. ctx.ssh.connect resolves the + // full host the connect pipeline uses, which carries it. + const connection = await ctx.ssh.connect(host.id, { + purpose: "fleet", + }); + try { + const sshClient = connection.client as Client; + const platform = await detectPlatform(sshClient); const cmd = action === "remove" - ? buildRemoveCommand(platform.pkg, packageName) + ? buildPackageRemoveCommand(platform.pkg, packageName) : buildPackageActionCommand( platform.pkg, action, @@ -1441,9 +1357,9 @@ router.post( try { const result = await execElevated( - client, + sshClient, cmd, - fullHost.sudoPassword, + connection.host.sudoPassword as string | undefined, { forceSudo: true, timeoutMs: 600000 }, ); return { @@ -1456,52 +1372,26 @@ router.post( } throw elevationError; } - }, - ); - }, - ); + } finally { + connection.dispose(); + } + }, + ); - if (!fleetFound) { - return res.status(404).json({ error: "Fleet not found" }); + if (!fleetFound) { + return res.status(404).json({ error: "Fleet not found" }); + } + + res.json({ results }); + } catch (err) { + logError( + ctx, + "Failed to run fleet package action", + err, + "fleet_packages_failed", + ); + res.status(500).json({ error: "Failed to run fleet package action" }); } - - res.json({ results }); - } catch (err) { - databaseLogger.error("Failed to run fleet package action", err, { - operation: "fleet_packages_failed", - userId, - fleetId, - }); - res.status(500).json({ error: "Failed to run fleet package action" }); - } - }, -); - -export function buildRemoveCommand( - pkg: "apt" | "dnf" | "yum" | "pacman" | null, - name: string, -): string | null { - switch (pkg) { - case "apt": - return `DEBIAN_FRONTEND=noninteractive apt-get -y remove ${name}`; - case "dnf": - return `dnf -y remove ${name}`; - case "yum": - return `yum -y remove ${name}`; - case "pacman": - return `pacman -R --noconfirm ${name}`; - default: - return null; - } -} - -/** Called from activate(). Mounts this router at /fleets via the shared - * dispatcher. */ -export function startFleetsService(mountOn: Router): void { - mountOn.use(router); -} - -/** Called from deactivate(). /fleets/* falls back to 404 until reactivated. */ -export function stopFleetsService(): void { - // Unmounted by the runtime when the plugin deactivates. + }, + ); } diff --git a/plugins/fleets/src/backend/ssh.ts b/plugins/fleets/src/backend/ssh.ts deleted file mode 100644 index b7a221543..000000000 --- a/plugins/fleets/src/backend/ssh.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Client } from "ssh2"; -import type { - PluginSsh, - PluginSshConnection, - PluginSshConnectOptions, - PluginSshHost, -} from "@termix/plugin-sdk/backend"; - -let current: PluginSsh | null = null; - -/** Set in activate, cleared on deactivate. */ -export function setPluginSsh(ssh: PluginSsh | null): void { - current = ssh; -} - -export function pluginSsh(): PluginSsh { - if (!current) throw new Error("The plugin is not active"); - return current; -} - -/** withConnection with ssh2's Client type filled in. */ -export function withSshConnection( - host: number | PluginSshHost, - options: PluginSshConnectOptions & { pool: string }, - fn: (client: Client) => Promise, -): Promise { - return pluginSsh().withConnection(host, options, fn); -} - -/** connect with ssh2's Client type filled in. */ -export function connectSsh( - host: number | PluginSshHost, - options?: PluginSshConnectOptions, -): Promise> { - return pluginSsh().connect(host, options); -} diff --git a/plugins/fleets/src/backend/tables.ts b/plugins/fleets/src/backend/tables.ts new file mode 100644 index 000000000..199038d9a --- /dev/null +++ b/plugins/fleets/src/backend/tables.ts @@ -0,0 +1,98 @@ +import { + adoptLegacyTable, + defineTable, + id, + integer, + refHost, + refUser, + text, +} from "@termix/plugin-sdk/db"; + +/** + * A fleet: a named group of hosts, by static membership or tag rules. + * + * Adopted from core's "fleets", so column and index names are the legacy + * ones: the rename carries the existing rows across. + */ +export const fleets = adoptLegacyTable( + "fleets", + defineTable("fleets", { + id: id(), + userId: refUser(), + name: text().notNull(), + description: text(), + color: text(), + icon: text(), + // JSON array of tag strings, unioned with static members at resolution + // time. Kept to tag-equality matching for v1. + tagRules: text(), + syncId: text().unique(), + createdAt: text().notNull().defaultNow(), + updatedAt: text().notNull().defaultNow(), + }), +); + +/** + * Static fleet membership. A host can also be an effective member through a + * tag rule match, resolved in the repository rather than stored here. + * + * fleetId has no declared refUser/refHost FK: the SDK's referenceable targets + * are core's users and ssh_data tables only, not another table this same + * plugin owns. The legacy foreign key to fleets(id) is preserved by the + * adoption migration's CREATE TABLE, which is why the migration file keeps + * the FOREIGN KEY clause the generator does not emit for this column. + */ +export const fleetMembers = adoptLegacyTable( + "fleet_members", + defineTable( + "members", + { + id: id(), + fleetId: integer().notNull(), + hostId: refHost(), + addedAt: text().notNull().defaultNow(), + }, + { + // fleetId leads the unique pair, so listing a fleet's hosts is already + // served. Finding the fleets a host belongs to starts from hostId. + uniques: [ + { + name: "idx_fleet_members_fleet_host", + columns: ["fleetId", "hostId"], + }, + ], + indexes: [{ name: "idx_fleet_members_host", columns: ["hostId"] }], + }, + ), +); + +// Latest-only inventory snapshot per host, overwritten on each refresh - no +// historical log, matching the "latest snapshot only" scope decision. +export const fleetInventory = adoptLegacyTable( + "fleet_inventory", + defineTable( + "inventory", + { + id: id(), + hostId: refHost(), + userId: refUser(), + osPrettyName: text(), + kernel: text(), + architecture: text(), + hostname: text(), + uptimeSeconds: integer(), + ip: text(), + packageManager: text(), + collectedAt: text().notNull().defaultNow(), + }, + { + // hostId leads the unique pair; a user's whole inventory reads by userId. + uniques: [ + { name: "idx_fleet_inventory_host", columns: ["hostId", "userId"] }, + ], + indexes: [{ name: "idx_fleet_inventory_user", columns: ["userId"] }], + }, + ), +); + +export const tables = [fleets, fleetMembers, fleetInventory]; diff --git a/plugins/fleets/src/frontend/FleetInventoryTab.tsx b/plugins/fleets/src/frontend/FleetInventoryTab.tsx index b3009c72d..6155ba0fb 100644 --- a/plugins/fleets/src/frontend/FleetInventoryTab.tsx +++ b/plugins/fleets/src/frontend/FleetInventoryTab.tsx @@ -1,6 +1,7 @@ -import { getErrorMessage } from "@/lib/error-message.js"; +import { getErrorMessage, cn } from "./helpers.js"; import { useEffect, useMemo, useState, useCallback } from "react"; import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { Input, Button } from "@termix/plugin-sdk/ui"; import { toast } from "sonner"; import { ArrowDown, @@ -11,16 +12,7 @@ import { Search, Server, } from "lucide-react"; -import { Input } from "@/components/input"; -import { Button } from "@/components/button"; -import { cn } from "@/lib/utils"; -import { - listFleets, - getFleetInventory, - refreshFleetInventory, - type FleetRow, - type FleetInventoryEntry, -} from "./fleets-api"; +import type { FleetsApi, FleetRow, FleetInventoryEntry } from "./fleets-api.js"; type SortKey = | "hostName" @@ -90,9 +82,11 @@ function SortHeader({ } export function FleetInventoryTab({ + api, fleetId, isVisible = true, }: { + api: FleetsApi; fleetId?: number; isVisible?: boolean; }) { @@ -113,10 +107,11 @@ export function FleetInventoryTab({ }, [fleetId]); useEffect(() => { - listFleets() + api + .list() .then(setFleets) .catch(() => {}); - }, []); + }, [api]); useEffect(() => { if (!fleets.length) return; @@ -129,7 +124,7 @@ export function FleetInventoryTab({ if (selectedFleetId === undefined) return; setLoading(true); try { - const data = await getFleetInventory(selectedFleetId); + const data = await api.inventory(selectedFleetId); setEntries(data); } catch (error) { const message = getErrorMessage(error, ""); @@ -137,7 +132,7 @@ export function FleetInventoryTab({ } finally { setLoading(false); } - }, [selectedFleetId, t]); + }, [api, selectedFleetId, t]); useEffect(() => { if (!isVisible) return; @@ -148,7 +143,7 @@ export function FleetInventoryTab({ if (selectedFleetId === undefined) return; setRefreshing(true); try { - const { results } = await refreshFleetInventory(selectedFleetId); + const { results } = await api.refreshInventory(selectedFleetId); const failed = results.filter((r) => !r.success).length; if (failed > 0) { toast.warning( diff --git a/plugins/fleets/src/frontend/FleetsPanel.tsx b/plugins/fleets/src/frontend/FleetsPanel.tsx index af3ca0ee9..7a43819d3 100644 --- a/plugins/fleets/src/frontend/FleetsPanel.tsx +++ b/plugins/fleets/src/frontend/FleetsPanel.tsx @@ -1,6 +1,33 @@ -import { getErrorMessage } from "@/lib/error-message.js"; +import { + getErrorMessage, + extractSnippetInputs, + type SnippetInput, +} from "./helpers.js"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; -import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { + useTranslation, + useHosts, + type PluginHostRecord, +} from "@termix/plugin-sdk/frontend"; +import { + Button, + Input, + Textarea, + Checkbox, + Badge, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogDescription, + FOLDER_COLORS, + Select2, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@termix/plugin-sdk/ui"; import { toast } from "sonner"; import { Boxes, @@ -23,61 +50,19 @@ import { User, X, } from "lucide-react"; -import { Button } from "@/components/button"; -import { Input } from "@/components/input"; -import { Textarea } from "@/components/textarea"; -import { Checkbox } from "@/components/checkbox"; -import { Badge } from "@/components/badge"; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - DialogDescription, -} from "@/components/dialog"; -import { FOLDER_COLORS } from "@/lib/theme"; -import { getSSHHosts } from "@/api/ssh-host-management-api"; -import { getUserList } from "@/api/user-management-api"; -import { - getRoles, - type SharePermissionLevel, - type ShareTarget, -} from "@/api/rbac-api"; -import { Select2 } from "@/components/select2"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/dropdown-menu"; -import { - extractSnippetInputs, - type SnippetInput, -} from "@/lib/snippet-variables"; -import { - listFleets, - createFleet, - updateFleet, - deleteFleet, - getFleetMembers, - addFleetMember, - removeFleetMember, - runFleetCommand, - pushFleetFile, - pullFleetFile, - getFleetInventory, - refreshFleetInventory, - runFleetPackageAction, - shareFleet, - type FleetRow, - type FleetMemberRow, - type FleetHostResult, - type FleetInventoryEntry, - type FleetPackageAction, - type FleetShareHostResult, -} from "./fleets-api"; -import type { SSHHost } from "@/types/index"; +import type { + FleetsApi, + FleetRow, + FleetMemberRow, + FleetHostResult, + FleetInventoryEntry, + FleetPackageAction, + FleetShareHostResult, + ShareableUser, + ShareableRole, + SharePermissionLevel, + ShareTarget, +} from "./fleets-api.js"; const SHARE_PERMISSION_LEVELS: SharePermissionLevel[] = [ "connect", @@ -144,11 +129,13 @@ function ResultsList({ results }: { results: FleetHostResult[] }) { } function FleetFormDialog({ + api, open, onClose, fleet, onSaved, }: { + api: FleetsApi; open: boolean; onClose: () => void; fleet: FleetRow | null; @@ -182,7 +169,7 @@ function FleetFormDialog({ setSaving(true); try { if (fleet) { - await updateFleet(fleet.id, { + await api.update(fleet.id, { name: name.trim(), description: description.trim() || null, color, @@ -190,7 +177,7 @@ function FleetFormDialog({ }); toast.success(t("newUi.sidebar.fleets.fleetUpdated")); } else { - await createFleet({ + await api.create({ name: name.trim(), description: description.trim() || null, color, @@ -304,6 +291,7 @@ function FleetFormDialog({ } function MemberPickerDialog({ + api, open, onClose, fleetId, @@ -311,10 +299,11 @@ function MemberPickerDialog({ memberIds, onChanged, }: { + api: FleetsApi; open: boolean; onClose: () => void; fleetId: number; - allHosts: SSHHost[]; + allHosts: PluginHostRecord[]; memberIds: Set; onChanged: () => void; }) { @@ -330,13 +319,14 @@ function MemberPickerDialog({ [allHosts, search], ); - async function toggleHost(host: SSHHost, isMember: boolean) { - setBusyId(host.id); + async function toggleHost(host: PluginHostRecord, isMember: boolean) { + const hostId = Number(host.id); + setBusyId(hostId); try { if (isMember) { - await removeFleetMember(fleetId, host.id); + await api.removeMember(fleetId, hostId); } else { - await addFleetMember(fleetId, host.id); + await api.addMember(fleetId, hostId); } onChanged(); } catch (error) { @@ -371,7 +361,8 @@ function MemberPickerDialog({
{visibleHosts.map((host) => { - const isMember = memberIds.has(host.id); + const hostId = Number(host.id); + const isMember = memberIds.has(hostId); return (
- {actionView === "run" && } - {actionView === "transfer" && } - {actionView === "inventory" && } - {actionView === "packages" && } + {actionView === "run" && ( + + )} + {actionView === "transfer" && ( + + )} + {actionView === "inventory" && ( + + )} + {actionView === "packages" && ( + + )} )} setPickerOpen(false)} fleetId={fleet.id} @@ -1372,6 +1359,7 @@ function FleetDetail({ /> setShareOpen(false)} fleet={fleet} @@ -1381,15 +1369,17 @@ function FleetDetail({ } export function FleetsPanel({ + api, active, onOpenFleetInventory, }: { + api: FleetsApi; active?: boolean; onOpenFleetInventory?: (fleetId: number) => void; }) { const { t } = useTranslation(); + const { hosts: allHosts } = useHosts(); const [fleets, setFleets] = useState([]); - const [allHosts, setAllHosts] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [selectedFleet, setSelectedFleet] = useState(null); @@ -1401,7 +1391,7 @@ export function FleetsPanel({ const loadFleets = useCallback(async () => { try { - const data = await listFleets(); + const data = await api.list(); setFleets(data); setSelectedFleet((prev) => prev ? (data.find((f) => f.id === prev.id) ?? null) : null, @@ -1410,28 +1400,24 @@ export function FleetsPanel({ const message = getErrorMessage(error, ""); toast.error(message || t("newUi.sidebar.fleets.loadFailed")); } - }, [t]); + }, [api, t]); useEffect(() => { if (!active || hasLoadedRef.current) return; hasLoadedRef.current = true; setLoading(true); - Promise.all([listFleets(), getSSHHosts({ includeStatus: false })]) - .then(([fleetData, hosts]) => { - setFleets(fleetData); - setAllHosts(hosts as unknown as SSHHost[]); - }) + loadFleets() .catch((error: unknown) => { const message = getErrorMessage(error, ""); toast.error(message || t("newUi.sidebar.fleets.loadFailed")); }) .finally(() => setLoading(false)); - }, [active, hasLoadedRef, t]); + }, [active, hasLoadedRef, loadFleets, t]); async function handleDelete() { if (!deleteTarget) return; try { - await deleteFleet(deleteTarget.id); + await api.remove(deleteTarget.id); toast.success(t("newUi.sidebar.fleets.fleetDeleted")); if (selectedFleet?.id === deleteTarget.id) setSelectedFleet(null); setDeleteTarget(null); @@ -1449,6 +1435,7 @@ export function FleetsPanel({ if (selectedFleet) { return ( setSelectedFleet(null)} @@ -1578,6 +1565,7 @@ export function FleetsPanel({ setFormOpen(false)} fleet={editingFleet} @@ -1585,6 +1573,7 @@ export function FleetsPanel({ /> setShareTarget(null)} fleet={shareTarget} diff --git a/plugins/fleets/src/frontend/fleets-api.ts b/plugins/fleets/src/frontend/fleets-api.ts index 8d87c66bf..059400ec4 100644 --- a/plugins/fleets/src/frontend/fleets-api.ts +++ b/plugins/fleets/src/frontend/fleets-api.ts @@ -1,9 +1,22 @@ -import { - authApi, - handleApiError, - type SharePermissionLevel, - type ShareTarget, -} from "@/main-axios"; +import type { PluginApiClient } from "@termix/plugin-sdk/frontend"; + +export type SharePermissionLevel = "connect" | "view" | "edit" | "manage"; + +export interface ShareTarget { + type: "user" | "role"; + id: string | number; +} + +export interface ShareableUser { + id: string; + username: string; +} + +export interface ShareableRole { + id: number; + name: string; + displayName: string | null; +} export interface FleetRow { id: number; @@ -25,7 +38,7 @@ export interface FleetMemberRow { ip: string; tags: string[]; static: boolean; - permissionLevel: "connect" | "view" | "edit" | "manage" | null; + permissionLevel: SharePermissionLevel | null; } export interface FleetHostResult { @@ -58,243 +71,145 @@ export interface FleetInventoryEntry { export type FleetPackageAction = "install" | "remove" | "upgrade-all"; -export async function listFleets(): Promise { - try { - const response = await authApi.get("/plugin-api/fleets"); - return response.data; - } catch (error) { - throw handleApiError(error, "fetch fleets"); - } -} - -export async function createFleet(fleetData: { - name: string; - description?: string | null; - color?: string | null; - icon?: string | null; - tagRules?: string[]; -}): Promise { - try { - const response = await authApi.post("/plugin-api/fleets", fleetData); - return response.data; - } catch (error) { - throw handleApiError(error, "create fleet"); - } -} - -export async function updateFleet( - fleetId: number, - fleetData: { - name?: string; - description?: string | null; - color?: string | null; - icon?: string | null; - tagRules?: string[]; - }, -): Promise { - try { - const response = await authApi.patch( - `/plugin-api/fleets/${fleetId}`, - fleetData, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "update fleet"); - } -} - -export async function deleteFleet( - fleetId: number, -): Promise<{ success: boolean }> { - try { - const response = await authApi.delete(`/plugin-api/fleets/${fleetId}`); - return response.data; - } catch (error) { - throw handleApiError(error, "delete fleet"); - } -} - -export async function getFleetMembers( - fleetId: number, -): Promise { - try { - const response = await authApi.get(`/plugin-api/fleets/${fleetId}/members`); - return response.data; - } catch (error) { - throw handleApiError(error, "fetch fleet members"); - } -} - -export async function addFleetMember( - fleetId: number, - hostId: number, -): Promise<{ success: boolean }> { - try { - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/members`, - { - hostId, - }, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "add fleet member"); - } -} - -export async function removeFleetMember( - fleetId: number, - hostId: number, -): Promise<{ success: boolean }> { - try { - const response = await authApi.delete( - `/plugin-api/fleets/${fleetId}/members/${hostId}`, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "remove fleet member"); - } -} - -export async function runFleetCommand( - fleetId: number, - command: string, - inputValues?: Record, -): Promise<{ results: FleetHostResult[] }> { - try { - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/execute`, - { - command, - ...(inputValues ? { inputValues } : {}), - }, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "run fleet command"); - } -} - -export async function pushFleetFile( - fleetId: number, - file: File, - remotePath: string, -): Promise<{ results: FleetHostResult[] }> { - try { - const form = new FormData(); - form.append("file", file); - form.append("remotePath", remotePath); - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/transfer/push`, - form, - { headers: { "Content-Type": "multipart/form-data" } }, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "push file to fleet"); - } -} - -export async function pullFleetFile( - fleetId: number, - remotePath: string, -): Promise<{ results: FleetHostResult[]; blob: Blob; fileName: string }> { - try { - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/transfer/pull`, - { remotePath }, - { responseType: "blob" }, - ); - - const resultsHeader = response.headers["x-fleet-transfer-results"]; - const results: FleetHostResult[] = resultsHeader - ? JSON.parse(atob(resultsHeader)) - : []; - - const disposition = response.headers["content-disposition"] as - string | undefined; - const match = disposition?.match(/filename="([^"]+)"/); - const fileName = match?.[1] ?? "fleet-transfer.zip"; - - return { results, blob: response.data, fileName }; - } catch (error) { - throw handleApiError(error, "pull file from fleet"); - } -} - -export async function getFleetInventory( - fleetId: number, -): Promise { - try { - const response = await authApi.get( - `/plugin-api/fleets/${fleetId}/inventory`, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "fetch fleet inventory"); - } -} - -export async function refreshFleetInventory( - fleetId: number, -): Promise<{ results: FleetHostResult[] }> { - try { - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/inventory`, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "refresh fleet inventory"); - } -} - -export async function runFleetPackageAction( - fleetId: number, - action: FleetPackageAction, - packageName?: string, -): Promise<{ results: FleetHostResult[] }> { - try { - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/packages`, - { - action, - ...(packageName ? { package: packageName } : {}), - }, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "run fleet package action"); - } -} - export interface FleetShareHostResult { hostId: number; shared: boolean; reason?: string; } -export async function shareFleet( - fleetId: number, - shareData: { - targets: ShareTarget[]; - permissionLevel: SharePermissionLevel; - durationHours?: number; - }, -): Promise<{ +export interface FleetShareResult { success: boolean; permissionLevel: SharePermissionLevel; expiresAt: string | null; hostsShared: number; hostsTotal: number; hostResults: FleetShareHostResult[]; -}> { - try { - const response = await authApi.post( - `/plugin-api/fleets/${fleetId}/share`, - shareData, - ); - return response.data; - } catch (error) { - throw handleApiError(error, "share fleet"); - } } + +/** + * The fleets routes, through the plugin's own client. Paths are relative to + * /plugin-api/fleets/, which the client already points at. + */ +export function createFleetsApi(api: PluginApiClient) { + const data = async (request: Promise<{ data: T }>) => (await request).data; + + return { + list: () => data(api.get("/")), + + create: (fleetData: { + name: string; + description?: string | null; + color?: string | null; + icon?: string | null; + tagRules?: string[]; + }) => data(api.post("/", fleetData)), + + update: ( + fleetId: number, + fleetData: { + name?: string; + description?: string | null; + color?: string | null; + icon?: string | null; + tagRules?: string[]; + }, + ) => data(api.patch(`/${fleetId}`, fleetData)), + + remove: (fleetId: number) => + data(api.delete<{ success: boolean }>(`/${fleetId}`)), + + members: (fleetId: number) => + data(api.get(`/${fleetId}/members`)), + + addMember: (fleetId: number, hostId: number) => + data(api.post<{ success: boolean }>(`/${fleetId}/members`, { hostId })), + + removeMember: (fleetId: number, hostId: number) => + data(api.delete<{ success: boolean }>(`/${fleetId}/members/${hostId}`)), + + runCommand: ( + fleetId: number, + command: string, + inputValues?: Record, + ) => + data( + api.post<{ results: FleetHostResult[] }>(`/${fleetId}/execute`, { + command, + ...(inputValues ? { inputValues } : {}), + }), + ), + + pushFile: async (fleetId: number, file: File, remotePath: string) => { + const form = new FormData(); + form.append("file", file); + form.append("remotePath", remotePath); + return data( + api.post<{ results: FleetHostResult[] }>( + `/${fleetId}/transfer/push`, + form, + { headers: { "Content-Type": "multipart/form-data" } }, + ), + ); + }, + + pullFile: async ( + fleetId: number, + remotePath: string, + ): Promise<{ + results: FleetHostResult[]; + blob: Blob; + fileName: string; + }> => { + const response = await api.post( + `/${fleetId}/transfer/pull`, + { remotePath }, + { responseType: "blob" }, + ); + const headers = (response as { headers?: Record }) + .headers; + const resultsHeader = headers?.["x-fleet-transfer-results"]; + const results: FleetHostResult[] = resultsHeader + ? JSON.parse(atob(resultsHeader)) + : []; + const disposition = headers?.["content-disposition"]; + const match = disposition?.match(/filename="([^"]+)"/); + const fileName = match?.[1] ?? "fleet-transfer.zip"; + return { results, blob: response.data, fileName }; + }, + + inventory: (fleetId: number) => + data(api.get(`/${fleetId}/inventory`)), + + refreshInventory: (fleetId: number) => + data(api.post<{ results: FleetHostResult[] }>(`/${fleetId}/inventory`)), + + runPackageAction: ( + fleetId: number, + action: FleetPackageAction, + packageName?: string, + ) => + data( + api.post<{ results: FleetHostResult[] }>(`/${fleetId}/packages`, { + action, + ...(packageName ? { package: packageName } : {}), + }), + ), + + share: ( + fleetId: number, + shareData: { + targets: ShareTarget[]; + permissionLevel: SharePermissionLevel; + durationHours?: number; + }, + ) => data(api.post(`/${fleetId}/share`, shareData)), + + shareTargetUsers: () => + data(api.get<{ users: ShareableUser[] }>("/share-targets/users")), + + shareTargetRoles: () => + data(api.get<{ roles: ShareableRole[] }>("/share-targets/roles")), + }; +} + +export type FleetsApi = ReturnType; diff --git a/plugins/fleets/src/frontend/helpers.ts b/plugins/fleets/src/frontend/helpers.ts new file mode 100644 index 000000000..564215094 --- /dev/null +++ b/plugins/fleets/src/frontend/helpers.ts @@ -0,0 +1,44 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +/** + * Extract a stable, human-readable message from an unknown thrown value. + * Mirrors src/ui/lib/error-message.ts - kept as a small standalone copy + * rather than a cross-plugin dependency for one pure function. + */ +export function getErrorMessage( + error: unknown, + fallback = "Unknown error", +): string { + return error instanceof Error ? error.message : fallback; +} + +export interface SnippetInput { + key: string; + label: string; +} + +const INPUT_PATTERN = + /\$\{INPUT_(\d+)(?::([^}$]+))?\}|\$INPUT_(\d+)(?![a-zA-Z0-9_])/g; + +/** + * Finds the $INPUT_n placeholders in a command, for the fleet run box's + * input fields. Mirrors src/ui/lib/snippet-variables.ts extractSnippetInputs. + */ +export function extractSnippetInputs(content: string): SnippetInput[] { + const seen = new Map(); + let match: RegExpExecArray | null; + INPUT_PATTERN.lastIndex = 0; + while ((match = INPUT_PATTERN.exec(content)) !== null) { + const digits = match[1] ?? match[3]; + const key = `INPUT_${digits}`; + if (!seen.has(key)) { + seen.set(key, { key, label: match[2]?.trim() || `Input ${digits}` }); + } + } + return Array.from(seen.values()); +} diff --git a/plugins/fleets/src/frontend/index.tsx b/plugins/fleets/src/frontend/index.tsx index 44314b249..7b453fde0 100644 --- a/plugins/fleets/src/frontend/index.tsx +++ b/plugins/fleets/src/frontend/index.tsx @@ -4,34 +4,38 @@ import type { TabProps, TermixApp, } from "@termix/plugin-sdk/frontend"; -import { FleetsPanel } from "./FleetsPanel"; -import { FleetInventoryTab } from "./FleetInventoryTab"; -import { listFleets } from "./fleets-api"; +import { FleetsPanel } from "./FleetsPanel.js"; +import { FleetInventoryTab } from "./FleetInventoryTab.js"; +import { createFleetsApi } from "./fleets-api.js"; const INVENTORY_TAB = "fleet-inventory"; -function Panel({ active, shell }: PanelProps) { - return ( - - shell.openSingletonTab(INVENTORY_TAB, { data: { fleetId } }) - } - /> - ); -} - -function InventoryTab({ tab, isVisible }: TabProps) { - const fleetId = tab.data?.fleetId; - return ( - - ); -} - export function activate(app: TermixApp): void { + const api = createFleetsApi(app.api); + + function Panel({ active, shell }: PanelProps) { + return ( + + shell.openSingletonTab(INVENTORY_TAB, { data: { fleetId } }) + } + /> + ); + } + + function InventoryTab({ tab, isVisible }: TabProps) { + const fleetId = tab.data?.fleetId; + return ( + + ); + } + app.registerRailItem({ id: "fleets", icon: Boxes, @@ -51,5 +55,5 @@ export function activate(app: TermixApp): void { }); // Other plugins (automations' host picker) list fleets through this. - app.registerAction("fleets.list", () => listFleets()); + app.registerAction("fleets.list", () => api.list()); } diff --git a/plugins/fleets/tests/backend/helpers.ts b/plugins/fleets/tests/backend/helpers.ts new file mode 100644 index 000000000..9cfc5a0ad --- /dev/null +++ b/plugins/fleets/tests/backend/helpers.ts @@ -0,0 +1,159 @@ +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { fileURLToPath } from "node:url"; +import express, { type Router } from "express"; +import { + createMockCtx, + createTestDb, + type MockPluginContext, + type TestDb, +} from "@termix/plugin-sdk/testing"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import manifestJson from "../../manifest.json"; + +export const pluginDir = fileURLToPath(new URL("../..", import.meta.url)); +export const manifest = manifestJson as unknown as PluginManifest; + +export interface TestServer { + db: TestDb; + mock: MockPluginContext; + request: ( + method: string, + path: string, + options?: { user?: string; body?: unknown }, + // Route bodies vary per test; asserting on them is the point. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) => Promise<{ status: number; body: any }>; + close: () => Promise; +} + +/** + * The plugin activated against a real in-memory database, its router mounted + * on an express app the way core mounts it: JSON parsed, the acting user set + * from the request, everything else left to the plugin. + */ +export async function startServer( + options: { + permissions?: string[]; + users?: string[]; + hosts?: Array<{ + id: number; + userId: string; + name?: string; + ip?: string; + tags?: string | null; + }>; + } = {}, +): Promise { + const hosts = options.hosts ?? [ + { id: 10, userId: "user-1", name: "host-a", ip: "10.0.0.1" }, + { id: 11, userId: "user-1", name: "host-b", ip: "10.0.0.2" }, + ]; + + const db = await createTestDb(pluginDir, { + before: (sqlite) => { + for (const user of options.users ?? ["user-1", "user-2"]) { + sqlite + .prepare("INSERT INTO users (id, username) VALUES (?, ?)") + .run(user, user); + } + // The plugin's fleet_members/fleet_inventory tables FK to ssh_data, + // which ctx.hosts (faked below) never touches - seed it directly so + // the constraint is satisfied the way it is against the real table. + for (const host of hosts) { + sqlite.prepare("INSERT INTO ssh_data (id) VALUES (?)").run(host.id); + } + }, + }); + + let router: Router | null = null; + const mock = createMockCtx({ + pluginId: manifest.id, + manifest, + capabilities: manifest.capabilities, + db: db.database, + router: () => (router = express.Router()), + permissions: options.permissions ?? [ + "fleets.view", + "fleets.manage", + "fleets.execute", + ], + }); + + // ctx.hosts is capability-checked separately from ctx.db; seed it with the + // same fixture hosts a real deployment would resolve through core. + mock.ctx.hosts.list = async () => + hosts.map((h) => ({ + id: h.id, + userId: h.userId, + name: h.name ?? null, + ip: h.ip ?? "", + port: 22, + username: "root", + tags: h.tags ?? null, + folder: null, + authType: "password", + })); + mock.ctx.hosts.get = async (hostId) => { + const found = hosts.find((h) => h.id === hostId); + if (!found) return null; + return { + id: found.id, + userId: found.userId, + name: found.name ?? null, + ip: found.ip ?? "", + port: 22, + username: "root", + tags: found.tags ?? null, + folder: null, + authType: "password", + }; + }; + mock.ctx.hosts.checkAccess = async (hostId) => { + const found = hosts.find((h) => h.id === hostId); + if (!found) return { hasAccess: false, isOwner: false, isShared: false }; + return { + hasAccess: true, + isOwner: true, + isShared: false, + permissionLevel: "manage", + }; + }; + + const { activate } = await import("../../src/backend/index.js"); + await activate(mock.ctx); + + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + mock.setActor(req.header("x-test-user") ?? undefined); + next(); + }); + app.use((req, res, next) => router!(req, res, next)); + + const server = http.createServer(app); + await new Promise((resolve) => server.listen(0, resolve)); + const { port } = server.address() as AddressInfo; + + return { + db, + mock, + async request(method, path, { user = "user-1", body: given } = {}) { + const body = method === "GET" ? undefined : given; + const response = await fetch(`http://127.0.0.1:${port}${path}`, { + method, + headers: { + "x-test-user": user, + ...(body !== undefined ? { "content-type": "application/json" } : {}), + }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + const text = await response.text(); + return { status: response.status, body: text ? JSON.parse(text) : null }; + }, + async close() { + await new Promise((resolve) => server.close(() => resolve())); + db.close(); + }, + }; +} diff --git a/plugins/fleets/tests/backend/migrations.test.ts b/plugins/fleets/tests/backend/migrations.test.ts new file mode 100644 index 000000000..427bdaedd --- /dev/null +++ b/plugins/fleets/tests/backend/migrations.test.ts @@ -0,0 +1,191 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing"; +import { pluginDir } from "./helpers"; + +// fleets/fleet_members/fleet_inventory as core's SQLite bootstrap created +// them before 2.9.0, plus the indexes performance-indexes.ts added. +const LEGACY_DDL = ` + CREATE TABLE fleets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name TEXT NOT NULL, + description TEXT, + color TEXT, + icon TEXT, + tag_rules TEXT, + sync_id TEXT UNIQUE, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE TABLE fleet_members ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + fleet_id INTEGER NOT NULL REFERENCES fleets(id) ON DELETE CASCADE, + host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE UNIQUE INDEX idx_fleet_members_fleet_host ON fleet_members(fleet_id, host_id); + CREATE INDEX idx_fleet_members_host ON fleet_members(host_id); + CREATE TABLE fleet_inventory ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + os_pretty_name TEXT, + kernel TEXT, + architecture TEXT, + hostname TEXT, + uptime_seconds INTEGER, + ip TEXT, + package_manager TEXT, + collected_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE UNIQUE INDEX idx_fleet_inventory_host ON fleet_inventory(host_id, user_id); + CREATE INDEX idx_fleet_inventory_user ON fleet_inventory(user_id); +`; + +let db: TestDb | null = null; + +afterEach(() => { + db?.close(); + db = null; +}); + +const tableExists = (name: string) => + !!db!.sqlite + .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?") + .get(name); + +const indexOn = (table: string) => + ( + db!.sqlite + .prepare( + "SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND name NOT LIKE 'sqlite_%'", + ) + .all(table) as { name: string }[] + ) + .map((row) => row.name) + .sort(); + +describe("adopting fleets, fleet_members, fleet_inventory", () => { + it("keeps every existing row when the legacy tables are there", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')"); + sqlite.exec("INSERT INTO ssh_data (id) VALUES (1), (2)"); + sqlite.exec(LEGACY_DDL); + sqlite + .prepare( + "INSERT INTO fleets (user_id, name, tag_rules, sync_id) VALUES (?, ?, ?, ?)", + ) + .run("user-1", "Web fleet", '["prod-web"]', "sync-a"); + sqlite + .prepare( + "INSERT INTO fleet_members (fleet_id, host_id) VALUES (1, 1)", + ) + .run(); + sqlite + .prepare( + "INSERT INTO fleet_inventory (host_id, user_id, os_pretty_name) VALUES (1, 'user-1', 'Debian')", + ) + .run(); + }, + }); + + expect(db.applied).toEqual(["0001_adopt_fleets"]); + expect(tableExists("fleets")).toBe(false); + expect(tableExists("fleet_members")).toBe(false); + expect(tableExists("fleet_inventory")).toBe(false); + expect(tableExists("p_fleets_fleets")).toBe(true); + expect(tableExists("p_fleets_members")).toBe(true); + expect(tableExists("p_fleets_inventory")).toBe(true); + + const fleetRows = db.sqlite + .prepare("SELECT user_id, name, tag_rules, sync_id FROM p_fleets_fleets") + .all(); + expect(fleetRows).toEqual([ + { + user_id: "user-1", + name: "Web fleet", + tag_rules: '["prod-web"]', + sync_id: "sync-a", + }, + ]); + + const memberRows = db.sqlite + .prepare("SELECT fleet_id, host_id FROM p_fleets_members") + .all(); + expect(memberRows).toEqual([{ fleet_id: 1, host_id: 1 }]); + + const inventoryRows = db.sqlite + .prepare( + "SELECT host_id, user_id, os_pretty_name FROM p_fleets_inventory", + ) + .all(); + expect(inventoryRows).toEqual([ + { host_id: 1, user_id: "user-1", os_pretty_name: "Debian" }, + ]); + + expect(indexOn("p_fleets_members")).toEqual( + ["idx_fleet_members_fleet_host", "idx_fleet_members_host"].sort(), + ); + expect(indexOn("p_fleets_inventory")).toEqual( + ["idx_fleet_inventory_host", "idx_fleet_inventory_user"].sort(), + ); + }); + + it("creates the tables fresh when there are no legacy ones", async () => { + db = await createTestDb(pluginDir); + + expect(tableExists("fleets")).toBe(false); + expect(tableExists("p_fleets_fleets")).toBe(true); + expect(tableExists("p_fleets_members")).toBe(true); + expect(tableExists("p_fleets_inventory")).toBe(true); + expect(db.sqlite.prepare("SELECT * FROM p_fleets_fleets").all()).toEqual( + [], + ); + }); + + it("cascades a fleet delete onto its member rows via the fleet_id FK", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1')"); + sqlite.exec("INSERT INTO ssh_data (id) VALUES (1)"); + }, + }); + db.sqlite + .prepare( + "INSERT INTO p_fleets_fleets (id, user_id, name) VALUES (1, 'user-1', 'Fleet')", + ) + .run(); + db.sqlite + .prepare("INSERT INTO p_fleets_members (fleet_id, host_id) VALUES (1, 1)") + .run(); + + db.sqlite.exec("DELETE FROM p_fleets_fleets WHERE id = 1"); + + expect(db.sqlite.prepare("SELECT * FROM p_fleets_members").all()).toEqual( + [], + ); + }); + + it("removes a user's fleets when the user is deleted", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')"); + }, + }); + const insert = db.sqlite.prepare( + "INSERT INTO p_fleets_fleets (user_id, name) VALUES (?, ?)", + ); + insert.run("user-1", "Mine"); + insert.run("user-2", "Theirs"); + + db.sqlite.exec("DELETE FROM users WHERE id = 'user-1'"); + + const names = ( + db.sqlite.prepare("SELECT name FROM p_fleets_fleets").all() as { + name: string; + }[] + ).map((row) => row.name); + expect(names).toEqual(["Theirs"]); + }); +}); diff --git a/plugins/fleets/tests/backend/repositories/fleet-repository.test.ts b/plugins/fleets/tests/backend/repositories/fleet-repository.test.ts deleted file mode 100644 index d0880f628..000000000 --- a/plugins/fleets/tests/backend/repositories/fleet-repository.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { TestSqliteDatabase } from "../../../../../src/backend/tests/database/repositories/test-support.js"; -import { FleetRepository } from "../../../../../src/backend/database/repositories/fleet-repository.js"; - -describe("FleetRepository", () => { - let adapter: TestSqliteDatabase | null = null; - - afterEach(async () => { - if (adapter) { - await adapter.close(); - adapter = null; - } - }); - - async function createRepository( - onWrite?: () => void | Promise, - ): Promise<{ repository: FleetRepository }> { - adapter = new TestSqliteDatabase(); - const context = await adapter.connect(); - await adapter.exec(` - INSERT INTO users (id, username, password_hash) - VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash'); - INSERT INTO ssh_data (id, user_id, name, ip, port, username, auth_type, tags) - VALUES - (1, 'user-1', 'web-1', '10.0.0.1', 22, 'root', 'password', 'prod-web,edge'), - (2, 'user-1', 'web-2', '10.0.0.2', 22, 'root', 'password', 'prod-web'), - (3, 'user-1', 'db-1', '10.0.0.3', 22, 'root', 'password', 'prod-db'), - (4, 'user-1', 'static-only', '10.0.0.4', 22, 'root', 'password', NULL), - (5, 'user-2', 'other-user-host', '10.0.0.5', 22, 'root', 'password', 'prod-web'); - `); - - return { repository: new FleetRepository(context, onWrite) }; - } - - it("unions static membership and tag-matched hosts, deduplicated by id", async () => { - let writes = 0; - const { repository } = await createRepository(() => { - writes += 1; - }); - - const fleet = await repository.create("user-1", { - name: "web fleet", - tagRules: ["prod-web"], - }); - // host 1 matches by tag AND is added statically - must appear once. - await repository.addMember(fleet.id, 1); - // host 4 has no tags - only reachable via static membership. - await repository.addMember(fleet.id, 4); - - const members = await repository.listEffectiveMembers("user-1", fleet.id); - const ids = members.map((m) => m.id).sort((a, b) => a - b); - - expect(ids).toEqual([1, 2, 4]); - expect(writes).toBeGreaterThan(0); - }); - - it("never returns another user's hosts even if tags match", async () => { - const { repository } = await createRepository(); - - const fleet = await repository.create("user-1", { - name: "web fleet", - tagRules: ["prod-web"], - }); - - const members = await repository.listEffectiveMembers("user-1", fleet.id); - expect(members.map((m) => m.id)).not.toContain(5); - }); - - it("returns only static members when a fleet has no tag rules", async () => { - const { repository } = await createRepository(); - - const fleet = await repository.create("user-1", { name: "static fleet" }); - await repository.addMember(fleet.id, 3); - - const members = await repository.listEffectiveMembers("user-1", fleet.id); - expect(members.map((m) => m.id)).toEqual([3]); - }); - - it("removeMember only drops the static row, not tag-based membership", async () => { - const { repository } = await createRepository(); - - const fleet = await repository.create("user-1", { - name: "web fleet", - tagRules: ["prod-web"], - }); - await repository.addMember(fleet.id, 1); - - await expect(repository.removeMember(fleet.id, 1)).resolves.toBe(true); - - // host 1 still matches the tag rule, so it remains an effective member. - const members = await repository.listEffectiveMembers("user-1", fleet.id); - expect(members.map((m) => m.id)).toContain(1); - }); - - it("returns an empty list for a fleet the caller does not own", async () => { - const { repository } = await createRepository(); - const fleet = await repository.create("user-1", { name: "private" }); - - await expect( - repository.listEffectiveMembers("user-2", fleet.id), - ).resolves.toEqual([]); - }); -}); diff --git a/plugins/fleets/tests/backend/repository.test.ts b/plugins/fleets/tests/backend/repository.test.ts new file mode 100644 index 000000000..980d16135 --- /dev/null +++ b/plugins/fleets/tests/backend/repository.test.ts @@ -0,0 +1,209 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing"; +import type { + PluginHostSummary, + PluginHosts, +} from "@termix/plugin-sdk/backend"; +import { pluginDir } from "./helpers"; +import { + fleets, + fleetMembers, + fleetInventory, +} from "../../src/backend/tables.js"; +import { createFleetRepository } from "../../src/backend/repository.js"; + +let db: TestDb | null = null; + +afterEach(() => { + db?.close(); + db = null; +}); + +function fakeHosts(hosts: PluginHostSummary[]): PluginHosts { + return { + list: async () => hosts, + get: async (hostId) => hosts.find((h) => h.id === hostId) ?? null, + checkAccess: async (hostId) => { + const found = hosts.find((h) => h.id === hostId); + return found + ? { + hasAccess: true, + isOwner: true, + isShared: false, + permissionLevel: "manage", + } + : { hasAccess: false, isOwner: false, isShared: false }; + }, + share: async (hostId) => ({ hostId, shared: true }), + listUsers: async () => [], + listRoles: async () => [], + }; +} + +async function setup(hosts: PluginHostSummary[]) { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')"); + sqlite.exec( + `INSERT INTO ssh_data (id) VALUES ${hosts.map((h) => `(${h.id})`).join(", ")}`, + ); + }, + }); + + const fleetsTable = await db.database.define(fleets); + const membersTable = await db.database.define(fleetMembers); + const inventoryTable = await db.database.define(fleetInventory); + const repo = createFleetRepository( + db.database, + fakeHosts(hosts), + fleetsTable, + membersTable, + inventoryTable, + ); + return repo; +} + +function host( + id: number, + overrides: Partial = {}, +): PluginHostSummary { + return { + id, + userId: "user-1", + name: `host-${id}`, + ip: `10.0.0.${id}`, + port: 22, + username: "root", + tags: null, + folder: null, + authType: "password", + ...overrides, + }; +} + +describe("FleetRepository.listEffectiveMembers", () => { + it("unions static membership and tag-matched hosts, deduplicated by id", async () => { + const repo = await setup([ + host(1, { tags: "prod-web,edge" }), + host(2, { tags: "prod-web" }), + host(3, { tags: "prod-db" }), + host(4, { tags: null }), + host(5, { userId: "user-2", tags: "prod-web" }), + ]); + + const fleet = await repo.create("user-1", { + name: "web fleet", + tagRules: ["prod-web"], + }); + // host 1 matches by tag AND is added statically - must appear once. + await repo.addMember(fleet.id, 1); + // host 4 has no tags - only reachable via static membership. + await repo.addMember(fleet.id, 4); + + const members = await repo.listEffectiveMembers("user-1", fleet.id); + const ids = members.map((m) => m.id).sort((a, b) => a - b); + + expect(ids).toEqual([1, 2, 4]); + }); + + it("never returns another user's hosts even if tags match", async () => { + const repo = await setup([ + host(1, { tags: "prod-web" }), + host(5, { userId: "user-2", tags: "prod-web" }), + ]); + + const fleet = await repo.create("user-1", { + name: "web fleet", + tagRules: ["prod-web"], + }); + + const members = await repo.listEffectiveMembers("user-1", fleet.id); + expect(members.map((m) => m.id)).not.toContain(5); + }); + + it("returns only static members when a fleet has no tag rules", async () => { + const repo = await setup([host(3, { tags: "prod-db" })]); + + const fleet = await repo.create("user-1", { name: "static fleet" }); + await repo.addMember(fleet.id, 3); + + const members = await repo.listEffectiveMembers("user-1", fleet.id); + expect(members.map((m) => m.id)).toEqual([3]); + }); + + it("removeMember only drops the static row, not tag-based membership", async () => { + const repo = await setup([host(1, { tags: "prod-web" })]); + + const fleet = await repo.create("user-1", { + name: "web fleet", + tagRules: ["prod-web"], + }); + await repo.addMember(fleet.id, 1); + + await expect(repo.removeMember(fleet.id, 1)).resolves.toBe(true); + + // host 1 still matches the tag rule, so it remains an effective member. + const members = await repo.listEffectiveMembers("user-1", fleet.id); + expect(members.map((m) => m.id)).toContain(1); + }); + + it("returns an empty list for a fleet the caller does not own", async () => { + const repo = await setup([host(1)]); + const fleet = await repo.create("user-1", { name: "private" }); + + await expect( + repo.listEffectiveMembers("user-2", fleet.id), + ).resolves.toEqual([]); + }); +}); + +describe("FleetRepository CRUD", () => { + it("creates, updates and deletes a fleet", async () => { + const repo = await setup([host(1)]); + const created = await repo.create("user-1", { + name: "Fleet", + tagRules: ["a"], + }); + expect(created.name).toBe("Fleet"); + expect(created.tagRules).toBe('["a"]'); + + const updated = await repo.update("user-1", created.id, { + name: "Renamed", + }); + expect(updated?.name).toBe("Renamed"); + + await expect(repo.delete("user-1", created.id)).resolves.toBe(true); + await expect(repo.findById("user-1", created.id)).resolves.toBeNull(); + }); + + it("upserts and lists inventory per host", async () => { + const repo = await setup([host(1)]); + await repo.upsertInventory("user-1", 1, { + osPrettyName: "Debian", + kernel: "6.1.0", + architecture: "x86_64", + hostname: "web-1", + uptimeSeconds: 100, + ip: "10.0.0.1", + packageManager: "apt", + }); + + const rows = await repo.listInventoryForHosts("user-1", [1]); + expect(rows).toHaveLength(1); + expect(rows[0]).toMatchObject({ hostId: 1, osPrettyName: "Debian" }); + + // Refreshing overwrites the latest-only snapshot rather than adding a row. + await repo.upsertInventory("user-1", 1, { + osPrettyName: "Ubuntu", + kernel: "6.2.0", + architecture: "x86_64", + hostname: "web-1", + uptimeSeconds: 200, + ip: "10.0.0.1", + packageManager: "apt", + }); + const refreshed = await repo.listInventoryForHosts("user-1", [1]); + expect(refreshed).toHaveLength(1); + expect(refreshed[0].osPrettyName).toBe("Ubuntu"); + }); +}); diff --git a/plugins/fleets/tests/backend/routes.test.ts b/plugins/fleets/tests/backend/routes.test.ts index 6d4e5fa29..7e9f8052f 100644 --- a/plugins/fleets/tests/backend/routes.test.ts +++ b/plugins/fleets/tests/backend/routes.test.ts @@ -1,388 +1,125 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import type { Request, Response, Router } from "express"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { startServer, type TestServer } from "./helpers"; +import { parseInventoryProbe } from "../../src/backend/routes.js"; -const state = vi.hoisted(() => ({ - currentUserId: "user-1", - fleets: new Map(), - members: new Map(), - hostAccess: new Map(), - hosts: new Map>(), -})); +let server: TestServer; -vi.mock("../../../../src/backend/database/db/index.js", () => ({ db: {} })); - -vi.mock("../../../../src/backend/utils/logger.js", () => ({ - authLogger: { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - success: vi.fn(), - }, - databaseLogger: { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - success: vi.fn(), - }, -})); - -vi.mock("../../../../src/backend/utils/auth-manager.js", () => ({ - AuthManager: { - getInstance: () => ({ - createAuthMiddleware: - () => - (req: Record, _res: unknown, next: () => void) => { - req.userId = state.currentUserId; - next(); - }, - createDataAccessMiddleware: - () => (_req: unknown, _res: unknown, next: () => void) => - next(), - }), - }, -})); - -vi.mock("../../../../src/backend/utils/permission-manager.js", () => ({ - PermissionManager: { - getInstance: () => ({ - canAccessHost: async (userId: string, hostId: number, level: string) => { - const key = `${userId}:${hostId}:${level}`; - const found = state.hostAccess.get(key); - if (found) return found; - // default: full access unless a test explicitly denies it - return { hasAccess: true, isOwner: true, permissionLevel: "manage" }; - }, - }), - }, -})); - -vi.mock("../../../../src/backend/hosts/host-resolver.js", () => ({ - resolveHostById: async (hostId: number) => state.hosts.get(hostId) ?? null, -})); - -vi.mock("../../src/backend/ssh.js", () => ({ - withSshConnection: async ( - _host: unknown, - _options: unknown, - fn: (client: unknown) => unknown, - ) => fn({}), -})); - -vi.mock("../../../../src/backend/hosts/metrics-shared/common-utils.js", () => ({ - execCommand: vi.fn(async () => ({ stdout: "ok", stderr: "", code: 0 })), -})); - -vi.mock("../../../../src/backend/hosts/metrics-shared/platform.js", () => ({ - detectPlatform: vi.fn(async () => ({ pkg: "apt", osPrettyName: "Debian" })), -})); - -vi.mock( - "../../../../src/backend/hosts/metrics-shared/exec-elevated.js", - async () => { - class ElevationError extends Error { - code: string; - constructor(code: string, message: string) { - super(message); - this.code = code; - } - } - return { - execElevated: vi.fn(), - ElevationError, - }; - }, -); - -vi.mock( - "../../../../src/backend/hosts/metrics-shared/package-commands.js", - () => ({ - buildPackageActionCommand: vi.fn(() => "apt-get install -y foo"), - }), -); - -vi.mock("../../../../src/backend/hosts/metrics-shared/validation.js", () => ({ - isValidPackageName: (v: unknown) => typeof v === "string" && v.length > 0, -})); - -vi.mock( - "../../../../src/backend/database/routes/snippets-execution.js", - () => ({ - resolveSnippetCommand: (command: string) => command, - }), -); - -vi.mock("../../../../src/backend/database/routes/rbac.js", () => ({ - isSharePermissionLevel: (v: unknown) => - ["connect", "view", "edit", "manage"].includes(v as string), - expiryFromDuration: () => null, - parseShareTargets: () => null, -})); - -vi.mock("../../../../src/backend/database/repositories/factory.js", () => ({ - createCurrentFleetRepository: () => ({ - findById: async (userId: string, fleetId: number) => { - const fleet = state.fleets.get(fleetId); - return fleet && fleet.userId === userId ? fleet : null; - }, - listEffectiveMembers: async (_userId: string, fleetId: number) => - state.members.get(fleetId) ?? [], - listStaticMemberIds: async () => [], - listByUser: async (userId: string) => - [...state.fleets.values()].filter((f) => f.userId === userId), - }), - createCurrentFleetInventoryRepository: () => ({ - listForHosts: async () => [], - upsert: async () => ({}), - }), - createCurrentRbacAccessRepository: () => ({}), - createCurrentRoleRepository: () => ({}), - createCurrentUserRepository: () => ({}), -})); - -vi.mock("../../../../src/backend/database/routes/fleet-dispatch.js", () => ({ - registerFleetsRouter: vi.fn(), - unregisterFleetsRouter: vi.fn(), -})); - -const { router, parseInventoryProbe, buildRemoveCommand } = - await import("../../src/backend/routes.js"); - -function findLayer(method: string, path: string) { - const stack = (router as unknown as Router).stack as Array<{ - route?: { - path: string; - methods: Record; - stack: Array<{ handle: (req: Request, res: Response) => unknown }>; - }; - }>; - const layer = stack.find( - (l) => l.route?.path === path && l.route?.methods[method], - ); - if (!layer?.route) throw new Error(`No route for ${method} ${path}`); - return layer.route.stack[layer.route.stack.length - 1].handle; -} - -function makeReqRes(overrides: { - body?: Record; - params?: Record; -}) { - const req = { - userId: state.currentUserId, - body: overrides.body ?? {}, - params: overrides.params ?? {}, - headers: {}, - } as unknown as Request; - - const res = { - statusCode: 200, - jsonBody: null as unknown, - status(code: number) { - (this as unknown as { statusCode: number }).statusCode = code; - return this; - }, - json(payload: unknown) { - (this as unknown as { jsonBody: unknown }).jsonBody = payload; - return this; - }, - } as unknown as Response & { statusCode: number; jsonBody: unknown }; - - return { req, res }; -} - -async function invoke( - method: string, - path: string, - overrides: { - body?: Record; - params?: Record; - } = {}, -) { - const handler = findLayer(method, path); - const { req, res } = makeReqRes(overrides); - await handler(req, res); - return res as unknown as { - statusCode: number; - jsonBody: Record | null; - }; -} - -beforeEach(() => { - state.currentUserId = "user-1"; - state.fleets = new Map([[1, { id: 1, userId: "user-1", name: "web fleet" }]]); - state.members = new Map([ - [ - 1, - [ - { id: 10, name: "host-a" }, - { id: 11, name: "host-b" }, - ], - ], - ]); - state.hostAccess = new Map(); - state.hosts = new Map([ - [ - 10, - { - id: 10, - userId: "user-1", - name: "host-a", - ip: "10.0.0.1", - port: 22, - username: "root", - sudoPassword: "secret", - }, - ], - [ - 11, - { - id: 11, - userId: "user-1", - name: "host-b", - ip: "10.0.0.2", - port: 22, - username: "root", - sudoPassword: "secret", - }, - ], - ]); +afterEach(async () => { + await server?.close(); }); -describe("GET /:id/members", () => { - it("404s for a fleet the caller does not own", async () => { - const res = await invoke("get", "/:id/members", { - params: { id: "999" }, +async function createFleet( + server: TestServer, + body: Record = { name: "Web fleet" }, +) { + const res = await server.request("POST", "/", { body }); + expect(res.status).toBe(200); + return res.body; +} + +describe("fleet CRUD routes", () => { + beforeEach(async () => { + server = await startServer(); + }); + + it("creates and lists fleets with member counts", async () => { + const created = await createFleet(server); + expect(created).toMatchObject({ name: "Web fleet", tagRules: [] }); + + const list = await server.request("GET", "/"); + expect(list.status).toBe(200); + expect(list.body).toHaveLength(1); + expect(list.body[0]).toMatchObject({ id: created.id, memberCount: 0 }); + }); + + it("400s on a missing name", async () => { + const res = await server.request("POST", "/", { body: {} }); + expect(res.status).toBe(400); + }); + + it("updates and deletes a fleet", async () => { + const created = await createFleet(server); + const updated = await server.request("PATCH", `/${created.id}`, { + body: { name: "Renamed" }, }); - expect(res.statusCode).toBe(404); + expect(updated.status).toBe(200); + expect(updated.body.name).toBe("Renamed"); + + const deleted = await server.request("DELETE", `/${created.id}`); + expect(deleted.status).toBe(200); + expect((await server.request("GET", "/")).body).toHaveLength(0); + }); + + it("404s for a fleet the caller does not own", async () => { + const res = await server.request("GET", "/999/members"); + expect(res.status).toBe(404); }); it("400s on a non-numeric fleet id", async () => { - const res = await invoke("get", "/:id/members", { - params: { id: "not-a-number" }, - }); - expect(res.statusCode).toBe(400); + const res = await server.request("GET", "/not-a-number/members"); + expect(res.status).toBe(400); }); }); -describe("POST /:id/execute", () => { - it("400s when command is missing", async () => { - const res = await invoke("post", "/:id/execute", { - params: { id: "1" }, - body: {}, - }); - expect(res.statusCode).toBe(400); +describe("fleet membership routes", () => { + beforeEach(async () => { + server = await startServer(); }); - it("reports per-host success with the {hostId, hostName, success} shape", async () => { - const res = await invoke("post", "/:id/execute", { - params: { id: "1" }, - body: { command: "uptime" }, - }); - expect(res.statusCode).toBe(200); - const results = res.jsonBody?.results as Array>; - expect(results).toHaveLength(2); - expect(results[0]).toMatchObject({ - hostId: 10, - hostName: "host-a", - success: true, + it("adds and lists members, then removes one", async () => { + const fleet = await createFleet(server); + const added = await server.request("POST", `/${fleet.id}/members`, { + body: { hostId: 10 }, }); + expect(added.status).toBe(200); + + const members = await server.request("GET", `/${fleet.id}/members`); + expect(members.status).toBe(200); + expect(members.body).toHaveLength(1); + expect(members.body[0]).toMatchObject({ id: 10, static: true }); + + const removed = await server.request("DELETE", `/${fleet.id}/members/10`); + expect(removed.status).toBe(200); + expect( + (await server.request("GET", `/${fleet.id}/members`)).body, + ).toHaveLength(0); }); - it("isolates one host's access denial - the other host still succeeds", async () => { - state.hostAccess.set("user-1:10:edit", { - hasAccess: false, - isOwner: false, + it("404s when adding a host outside the fixture host list", async () => { + const fleet = await createFleet(server); + const res = await server.request("POST", `/${fleet.id}/members`, { + body: { hostId: 999 }, }); - - const res = await invoke("post", "/:id/execute", { - params: { id: "1" }, - body: { command: "uptime" }, - }); - - const results = res.jsonBody?.results as Array>; - const denied = results.find((r) => r.hostId === 10); - const allowed = results.find((r) => r.hostId === 11); - expect(denied).toMatchObject({ success: false }); - expect(String(denied?.error)).toMatch(/edit/); - expect(allowed).toMatchObject({ success: true }); - }); - - it("404s for a fleet the caller does not own", async () => { - const res = await invoke("post", "/:id/execute", { - params: { id: "999" }, - body: { command: "uptime" }, - }); - expect(res.statusCode).toBe(404); + expect(res.status).toBe(404); }); }); -describe("POST /:id/packages", () => { - it("400s on an invalid action", async () => { - const res = await invoke("post", "/:id/packages", { - params: { id: "1" }, - body: { action: "reformat-disk" }, +describe("permission gates", () => { + it("view routes 403 without fleets.view", async () => { + server = await startServer({ + permissions: ["fleets.manage", "fleets.execute"], }); - expect(res.statusCode).toBe(400); + const res = await server.request("GET", "/"); + expect(res.status).toBe(403); }); - it("surfaces an ElevationError as a per-host error, not a request failure", async () => { - const { execElevated, ElevationError } = - await import("../../../../src/backend/hosts/metrics-shared/exec-elevated.js"); - (execElevated as ReturnType).mockRejectedValue( - new ElevationError("SUDO_REQUIRED", "sudo password required"), - ); - - const res = await invoke("post", "/:id/packages", { - params: { id: "1" }, - body: { action: "install", package: "curl" }, + it("manage routes 403 without fleets.manage", async () => { + server = await startServer({ + permissions: ["fleets.view", "fleets.execute"], }); - - expect(res.statusCode).toBe(200); - const results = res.jsonBody?.results as Array>; - expect(results.every((r) => r.success === false)).toBe(true); - expect(results[0].error).toMatch(/sudo password required/); + const res = await server.request("POST", "/", { body: { name: "x" } }); + expect(res.status).toBe(403); }); - it("requires manage-level access, not just edit", async () => { - state.hostAccess.set("user-1:10:manage", { - hasAccess: false, - isOwner: false, + it("execute routes 403 without fleets.execute", async () => { + server = await startServer({ + permissions: ["fleets.view", "fleets.manage"], }); - state.hostAccess.set("user-1:11:manage", { - hasAccess: false, - isOwner: false, + const fleet = await createFleet(server); + const res = await server.request("POST", `/${fleet.id}/execute`, { + body: { command: "uptime" }, }); - - const { execElevated } = - await import("../../../../src/backend/hosts/metrics-shared/exec-elevated.js"); - (execElevated as ReturnType).mockResolvedValue({ - code: 0, - stdout: "done", - stderr: "", - }); - - const res = await invoke("post", "/:id/packages", { - params: { id: "1" }, - body: { action: "upgrade-all" }, - }); - - const results = res.jsonBody?.results as Array>; - expect(results.every((r) => r.success === false)).toBe(true); - expect(String(results[0].error)).toMatch(/manage/); - }); -}); - -describe("POST /:id/members", () => { - it("404s when the caller cannot access the target host", async () => { - state.hostAccess.set("user-1:99:connect", { - hasAccess: false, - isOwner: false, - }); - - const res = await invoke("post", "/:id/members", { - params: { id: "1" }, - body: { hostId: 99 }, - }); - expect(res.statusCode).toBe(404); + expect(res.status).toBe(403); }); }); @@ -413,8 +150,7 @@ describe("parseInventoryProbe", () => { }); it("nulls uptimeSeconds when the value is not a bare integer", () => { - const out = "uptime_seconds="; - expect(parseInventoryProbe(out).uptimeSeconds).toBeNull(); + expect(parseInventoryProbe("uptime_seconds=").uptimeSeconds).toBeNull(); }); it("ignores lines with no '=' separator", () => { @@ -423,19 +159,18 @@ describe("parseInventoryProbe", () => { }); }); -describe("buildRemoveCommand", () => { - it("builds the correct remove command per package manager", () => { - expect(buildRemoveCommand("apt", "curl")).toContain( - "apt-get -y remove curl", - ); - expect(buildRemoveCommand("dnf", "curl")).toBe("dnf -y remove curl"); - expect(buildRemoveCommand("yum", "curl")).toBe("yum -y remove curl"); - expect(buildRemoveCommand("pacman", "curl")).toBe( - "pacman -R --noconfirm curl", - ); +describe("share target pickers", () => { + beforeEach(async () => { + server = await startServer(); }); - it("returns null when no package manager was detected", () => { - expect(buildRemoveCommand(null, "curl")).toBeNull(); + it("lists users and roles through ctx.hosts", async () => { + const users = await server.request("GET", "/share-targets/users"); + expect(users.status).toBe(200); + expect(users.body).toEqual({ users: [] }); + + const roles = await server.request("GET", "/share-targets/roles"); + expect(roles.status).toBe(200); + expect(roles.body).toEqual({ roles: [] }); }); }); diff --git a/scripts/plugin-boundary-allowlist.json b/scripts/plugin-boundary-allowlist.json index fa2899ed2..58c955d43 100644 --- a/scripts/plugin-boundary-allowlist.json +++ b/scripts/plugin-boundary-allowlist.json @@ -31,9 +31,6 @@ "plugins/docker/src/frontend/components/LogViewer.tsx", "plugins/docker/src/frontend/docker-api.ts", "plugins/docker/src/frontend/index.tsx", - "plugins/fleets/src/frontend/FleetInventoryTab.tsx", - "plugins/fleets/src/frontend/FleetsPanel.tsx", - "plugins/fleets/src/frontend/fleets-api.ts", "plugins/host-metrics/src/frontend/HostEditorStatsTab.tsx", "plugins/host-metrics/src/frontend/HostMetricsApp.tsx", "plugins/host-metrics/src/frontend/HostMetricsTab.tsx", @@ -136,7 +133,6 @@ "plugins/docker/src/backend/index.ts", "plugins/docker/src/backend/routes.ts", "plugins/docker/src/backend/session-manager.ts", - "plugins/fleets/src/backend/routes.ts", "plugins/host-metrics/src/backend/automation-bridge.ts", "plugins/host-metrics/src/backend/history-routes.ts", "plugins/host-metrics/src/backend/managers/cron.ts", diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index a8cfe72af..174003a72 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -2664,87 +2664,6 @@ const migrateSchema = () => { } // --- homepage end --- - // --- fleets begin --- - try { - sqlite.prepare("SELECT id FROM fleets LIMIT 1").get(); - } catch { - try { - sqlite.exec(` - CREATE TABLE IF NOT EXISTS fleets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - name TEXT NOT NULL, - description TEXT, - color TEXT, - icon TEXT, - tag_rules TEXT, - sync_id TEXT UNIQUE, - created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - `); - } catch (createError) { - databaseLogger.warn("Failed to create fleets table", { - operation: "schema_migration", - error: createError, - }); - } - } - - try { - sqlite.prepare("SELECT id FROM fleet_members LIMIT 1").get(); - } catch { - try { - sqlite.exec(` - CREATE TABLE IF NOT EXISTS fleet_members ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - fleet_id INTEGER NOT NULL REFERENCES fleets(id) ON DELETE CASCADE, - host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, - added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - `); - sqlite.exec( - "CREATE UNIQUE INDEX IF NOT EXISTS idx_fleet_members_fleet_host ON fleet_members(fleet_id, host_id)", - ); - } catch (createError) { - databaseLogger.warn("Failed to create fleet_members table", { - operation: "schema_migration", - error: createError, - }); - } - } - - try { - sqlite.prepare("SELECT id FROM fleet_inventory LIMIT 1").get(); - } catch { - try { - sqlite.exec(` - CREATE TABLE IF NOT EXISTS fleet_inventory ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, - user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - os_pretty_name TEXT, - kernel TEXT, - architecture TEXT, - hostname TEXT, - uptime_seconds INTEGER, - ip TEXT, - package_manager TEXT, - collected_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - `); - sqlite.exec( - "CREATE UNIQUE INDEX IF NOT EXISTS idx_fleet_inventory_host ON fleet_inventory(host_id, user_id)", - ); - } catch (createError) { - databaseLogger.warn("Failed to create fleet_inventory table", { - operation: "schema_migration", - error: createError, - }); - } - } - // --- fleets end --- - // --- sync begin --- // Stable per-row identity used to match rows across two independently- // seeded databases (the embedded desktop backend and a connected remote diff --git a/src/backend/database/db/performance-indexes.ts b/src/backend/database/db/performance-indexes.ts index e065bfea9..a3c27fdfd 100644 --- a/src/backend/database/db/performance-indexes.ts +++ b/src/backend/database/db/performance-indexes.ts @@ -168,23 +168,6 @@ export const PERFORMANCE_INDEXES: PerformanceIndex[] = [ columns: "host_id", }, - // Fleets. - { - name: "idx_fleet_members_fleet", - table: "fleet_members", - columns: "fleet_id", - }, - { - name: "idx_fleet_members_host", - table: "fleet_members", - columns: "host_id", - }, - { - name: "idx_fleet_inventory_user", - table: "fleet_inventory", - columns: "user_id", - }, - // Automations. { name: "idx_automations_user", diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index 7c910a245..f3a7bfab2 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -1616,81 +1616,6 @@ export const homepageLayouts = mysqlTable("homepage_layouts", { }); // --- homepage end --- -// --- fleets begin --- -export const fleets = mysqlTable("fleets", { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - description: text("description"), - color: text("color"), - icon: text("icon"), - // JSON array of { tag: string } rules, unioned with static fleetMembers at - // resolution time. Kept to tag-equality matching for v1. - tagRules: text("tag_rules"), - syncId: varchar("sync_id", { length: 255 }).unique(), - createdAt: varchar("created_at", { length: 255 }) - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - updatedAt: varchar("updated_at", { length: 255 }) - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), -}); - -export const fleetMembers = mysqlTable( - "fleet_members", - { - id: int("id").autoincrement().primaryKey(), - fleetId: int("fleet_id") - .notNull() - .references(() => fleets.id, { onDelete: "cascade" }), - hostId: int("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - addedAt: text("added_at") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - }, - // fleet_id leads the unique pair, so listing a fleet's hosts is already - // served. Finding the fleets a host belongs to starts from host_id. - (table) => [ - uniqueIndex("idx_fleet_members_fleet_host").on(table.fleetId, table.hostId), - index("idx_fleet_members_host").on(table.hostId), - ], -); - -// Latest-only inventory snapshot per host, overwritten on each refresh - no -// historical log, matching the "latest snapshot only" scope decision. -export const fleetInventory = mysqlTable( - "fleet_inventory", - { - id: int("id").autoincrement().primaryKey(), - hostId: int("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - osPrettyName: text("os_pretty_name"), - kernel: text("kernel"), - architecture: text("architecture"), - hostname: text("hostname"), - uptimeSeconds: int("uptime_seconds"), - ip: text("ip"), - packageManager: text("package_manager"), - collectedAt: text("collected_at") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - }, - // host_id leads the unique pair; a user's whole inventory is read by user_id. - (table) => [ - uniqueIndex("idx_fleet_inventory_host").on(table.hostId, table.userId), - index("idx_fleet_inventory_user").on(table.userId), - ], -); -// --- fleets end --- - // --- sync begin --- diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index d54269c50..cd5e3aedd 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -1617,81 +1617,6 @@ export const homepageLayouts = pgTable("homepage_layouts", { }); // --- homepage end --- -// --- fleets begin --- -export const fleets = pgTable("fleets", { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - description: text("description"), - color: text("color"), - icon: text("icon"), - // JSON array of { tag: string } rules, unioned with static fleetMembers at - // resolution time. Kept to tag-equality matching for v1. - tagRules: text("tag_rules"), - syncId: varchar("sync_id", { length: 255 }).unique(), - createdAt: varchar("created_at", { length: 255 }) - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - updatedAt: varchar("updated_at", { length: 255 }) - .notNull() - .default(sql`CURRENT_TIMESTAMP`), -}); - -export const fleetMembers = pgTable( - "fleet_members", - { - id: serial("id").primaryKey(), - fleetId: integer("fleet_id") - .notNull() - .references(() => fleets.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - addedAt: text("added_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - // fleet_id leads the unique pair, so listing a fleet's hosts is already - // served. Finding the fleets a host belongs to starts from host_id. - (table) => [ - uniqueIndex("idx_fleet_members_fleet_host").on(table.fleetId, table.hostId), - index("idx_fleet_members_host").on(table.hostId), - ], -); - -// Latest-only inventory snapshot per host, overwritten on each refresh - no -// historical log, matching the "latest snapshot only" scope decision. -export const fleetInventory = pgTable( - "fleet_inventory", - { - id: serial("id").primaryKey(), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - osPrettyName: text("os_pretty_name"), - kernel: text("kernel"), - architecture: text("architecture"), - hostname: text("hostname"), - uptimeSeconds: integer("uptime_seconds"), - ip: text("ip"), - packageManager: text("package_manager"), - collectedAt: text("collected_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - // host_id leads the unique pair; a user's whole inventory is read by user_id. - (table) => [ - uniqueIndex("idx_fleet_inventory_host").on(table.hostId, table.userId), - index("idx_fleet_inventory_user").on(table.userId), - ], -); -// --- fleets end --- - // --- sync begin --- diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index 59954195d..120d2f155 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -1615,81 +1615,6 @@ export const homepageLayouts = sqliteTable("homepage_layouts", { }); // --- homepage end --- -// --- fleets begin --- -export const fleets = sqliteTable("fleets", { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - name: text("name").notNull(), - description: text("description"), - color: text("color"), - icon: text("icon"), - // JSON array of { tag: string } rules, unioned with static fleetMembers at - // resolution time. Kept to tag-equality matching for v1. - tagRules: text("tag_rules"), - syncId: text("sync_id").unique(), - createdAt: text("created_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - updatedAt: text("updated_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), -}); - -export const fleetMembers = sqliteTable( - "fleet_members", - { - id: integer("id").primaryKey({ autoIncrement: true }), - fleetId: integer("fleet_id") - .notNull() - .references(() => fleets.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - addedAt: text("added_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - // fleet_id leads the unique pair, so listing a fleet's hosts is already - // served. Finding the fleets a host belongs to starts from host_id. - (table) => [ - uniqueIndex("idx_fleet_members_fleet_host").on(table.fleetId, table.hostId), - index("idx_fleet_members_host").on(table.hostId), - ], -); - -// Latest-only inventory snapshot per host, overwritten on each refresh - no -// historical log, matching the "latest snapshot only" scope decision. -export const fleetInventory = sqliteTable( - "fleet_inventory", - { - id: integer("id").primaryKey({ autoIncrement: true }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - osPrettyName: text("os_pretty_name"), - kernel: text("kernel"), - architecture: text("architecture"), - hostname: text("hostname"), - uptimeSeconds: integer("uptime_seconds"), - ip: text("ip"), - packageManager: text("package_manager"), - collectedAt: text("collected_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - // host_id leads the unique pair; a user's whole inventory is read by user_id. - (table) => [ - uniqueIndex("idx_fleet_inventory_host").on(table.hostId, table.userId), - index("idx_fleet_inventory_user").on(table.userId), - ], -); -// --- fleets end --- - // --- sync begin --- diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts index 3a1c67920..ae9261574 100644 --- a/src/backend/database/repositories/factory.ts +++ b/src/backend/database/repositories/factory.ts @@ -15,8 +15,6 @@ import { CredentialRepository } from "./credential-repository.js"; import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js"; import { DismissedAlertRepository } from "./dismissed-alert-repository.js"; import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js"; -import { FleetRepository } from "./fleet-repository.js"; -import { FleetInventoryRepository } from "./fleet-inventory-repository.js"; import { HomepageItemRepository } from "./homepage-item-repository.js"; import { HomepageLayoutRepository } from "./homepage-layout-repository.js"; import { HostFolderRepository } from "./host-folder-repository.js"; @@ -239,20 +237,6 @@ export function createCurrentFileManagerBookmarkRepository(): FileManagerBookmar ); } -export function createCurrentFleetRepository(): FleetRepository { - return new FleetRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("fleet_repository_write"), - ); -} - -export function createCurrentFleetInventoryRepository(): FleetInventoryRepository { - return new FleetInventoryRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("fleet_inventory_repository_write"), - ); -} - export function createCurrentHomepageItemRepository(): HomepageItemRepository { return new HomepageItemRepository( createCurrentRepositoryContext(), diff --git a/src/backend/database/repositories/fleet-inventory-repository.ts b/src/backend/database/repositories/fleet-inventory-repository.ts deleted file mode 100644 index f01691144..000000000 --- a/src/backend/database/repositories/fleet-inventory-repository.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { and, eq, inArray } from "drizzle-orm"; -import { fleetInventory } from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { insertReturning, updateReturning } from "./returning.js"; - -export type FleetInventoryRecord = typeof fleetInventory.$inferSelect; - -export interface FleetInventoryInput { - osPrettyName: string | null; - kernel: string | null; - architecture: string | null; - hostname: string | null; - uptimeSeconds: number | null; - ip: string | null; - packageManager: string | null; -} - -export class FleetInventoryRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async upsert( - userId: string, - hostId: number, - input: FleetInventoryInput, - now = new Date().toISOString(), - ): Promise { - const existing = await this.findOne(userId, hostId); - - if (existing) { - const [updated] = await updateReturning( - this.context, - fleetInventory, - { ...input, collectedAt: now }, - eq(fleetInventory.id, existing.id), - ); - await this.afterWrite(); - return updated; - } - - const [created] = await insertReturning(this.context, fleetInventory, { - userId, - hostId, - ...input, - collectedAt: now, - }); - - await this.afterWrite(); - return created; - } - - async findOne( - userId: string, - hostId: number, - ): Promise { - const rows = await this.context.drizzle - .select() - .from(fleetInventory) - .where( - and( - eq(fleetInventory.userId, userId), - eq(fleetInventory.hostId, hostId), - ), - ) - .limit(1); - return rows[0] ?? null; - } - - async listForHosts( - userId: string, - hostIds: number[], - ): Promise { - if (hostIds.length === 0) return []; - return this.context.drizzle - .select() - .from(fleetInventory) - .where( - and( - eq(fleetInventory.userId, userId), - inArray(fleetInventory.hostId, hostIds), - ), - ); - } - - private async afterWrite(): Promise { - await this.onWrite?.(); - } -} diff --git a/src/backend/database/repositories/fleet-repository.ts b/src/backend/database/repositories/fleet-repository.ts deleted file mode 100644 index 2761bd5ce..000000000 --- a/src/backend/database/repositories/fleet-repository.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { and, eq } from "drizzle-orm"; -import { randomUUID } from "crypto"; -import { fleets, fleetMembers, hosts } from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { rowsAffected } from "./mutation-result.js"; -import { - deleteReturning, - insertReturning, - updateReturning, -} from "./returning.js"; - -export type FleetRecord = typeof fleets.$inferSelect; -export type FleetMemberHostRecord = typeof hosts.$inferSelect; - -export interface FleetCreateInput { - name: string; - description?: string | null; - color?: string | null; - icon?: string | null; - tagRules?: string[]; -} - -export interface FleetUpdateInput { - name?: string; - description?: string | null; - color?: string | null; - icon?: string | null; - tagRules?: string[]; -} - -function parseTagRules(raw: string | null): string[] { - if (!raw) return []; - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) - ? parsed.filter((t): t is string => typeof t === "string") - : []; - } catch { - return []; - } -} - -export class FleetRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async listByUser(userId: string): Promise { - return this.context.drizzle - .select() - .from(fleets) - .where(eq(fleets.userId, userId)); - } - - async findById(userId: string, fleetId: number): Promise { - const rows = await this.context.drizzle - .select() - .from(fleets) - .where(and(eq(fleets.id, fleetId), eq(fleets.userId, userId))) - .limit(1); - return rows[0] ?? null; - } - - async create( - userId: string, - input: FleetCreateInput, - now = new Date().toISOString(), - ): Promise { - const [created] = await insertReturning(this.context, fleets, { - userId, - name: input.name, - description: input.description ?? null, - color: input.color ?? null, - icon: input.icon ?? null, - tagRules: input.tagRules ? JSON.stringify(input.tagRules) : null, - syncId: randomUUID(), - createdAt: now, - updatedAt: now, - }); - - await this.afterWrite(); - return created; - } - - async update( - userId: string, - fleetId: number, - input: FleetUpdateInput, - now = new Date().toISOString(), - ): Promise { - const existing = await this.findById(userId, fleetId); - if (!existing) return null; - - const [updated] = await updateReturning( - this.context, - fleets, - { - name: input.name ?? existing.name, - description: - input.description === undefined - ? existing.description - : input.description, - color: input.color === undefined ? existing.color : input.color, - icon: input.icon === undefined ? existing.icon : input.icon, - tagRules: - input.tagRules === undefined - ? existing.tagRules - : JSON.stringify(input.tagRules), - updatedAt: now, - }, - and(eq(fleets.id, fleetId), eq(fleets.userId, userId)), - ); - - await this.afterWrite(); - return updated ?? null; - } - - async delete(userId: string, fleetId: number): Promise { - const deleted = await deleteReturning( - this.context, - fleets, - and(eq(fleets.id, fleetId), eq(fleets.userId, userId)), - ); - - if (deleted.length > 0) { - await this.afterWrite(); - return true; - } - return false; - } - - async addMember( - fleetId: number, - hostId: number, - now = new Date().toISOString(), - ): Promise { - const existing = await this.context.drizzle - .select({ id: fleetMembers.id }) - .from(fleetMembers) - .where( - and(eq(fleetMembers.fleetId, fleetId), eq(fleetMembers.hostId, hostId)), - ) - .limit(1); - - if (existing.length > 0) return; - - await this.context.drizzle.insert(fleetMembers).values({ - fleetId, - hostId, - addedAt: now, - }); - - await this.afterWrite(); - } - - async removeMember(fleetId: number, hostId: number): Promise { - const result = await this.context.drizzle - .delete(fleetMembers) - .where( - and(eq(fleetMembers.fleetId, fleetId), eq(fleetMembers.hostId, hostId)), - ); - - const affected = rowsAffected(result) > 0; - if (affected) await this.afterWrite(); - return affected; - } - - async listStaticMemberIds(fleetId: number): Promise { - const rows = await this.context.drizzle - .select({ hostId: fleetMembers.hostId }) - .from(fleetMembers) - .where(eq(fleetMembers.fleetId, fleetId)); - return rows.map((r) => r.hostId); - } - - /** - * Effective membership = static fleetMembers rows union hosts whose - * comma-separated tags string intersects any tag in the fleet's tagRules. - * Both sets are scoped to the fleet owner's hosts and deduplicated by id. - */ - async listEffectiveMembers( - userId: string, - fleetId: number, - ): Promise { - const fleet = await this.findById(userId, fleetId); - if (!fleet) return []; - - const staticIds = await this.listStaticMemberIds(fleetId); - const tagRules = parseTagRules(fleet.tagRules); - - const ownedHosts = await this.context.drizzle - .select() - .from(hosts) - .where(eq(hosts.userId, userId)); - - const byId = new Map(); - - for (const host of ownedHosts) { - if (staticIds.includes(host.id)) { - byId.set(host.id, host); - continue; - } - if (tagRules.length === 0) continue; - const hostTags = (host.tags ?? "") - .split(",") - .map((t) => t.trim()) - .filter(Boolean); - if (hostTags.some((tag) => tagRules.includes(tag))) { - byId.set(host.id, host); - } - } - - // Static members can reference hosts the fleet owner no longer owns - // (rare, but the FK cascade only fires on host delete, not transfer) - - // filter those out rather than surface a partial/foreign row. - return Array.from(byId.values()); - } - - async deleteByUserId(userId: string): Promise { - const userFleets = await this.listByUser(userId); - const result = await this.context.drizzle - .delete(fleets) - .where(eq(fleets.userId, userId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - return userFleets.length; - } - - private async afterWrite(): Promise { - await this.onWrite?.(); - } -} diff --git a/src/backend/plugins/ctx-hosts.ts b/src/backend/plugins/ctx-hosts.ts new file mode 100644 index 000000000..41c1bed26 --- /dev/null +++ b/src/backend/plugins/ctx-hosts.ts @@ -0,0 +1,301 @@ +/** + * ctx.hosts: the hosts a plugin's acting user can see, and the sharing + * operations that need core's RBAC and secret-snapshot machinery. + * + * list/get/checkAccess need hosts:read. share and the share-target pickers + * need hosts:write, because granting access to a host is a write on that + * host even though the plugin owns neither the host nor the grant. + */ + +import type { + PluginHosts, + PluginHostSummary, + PluginHostAccess, + PluginHostShareLevel, + PluginHostShareResult, + PluginShareTarget, + PluginShareableUser, + PluginShareableRole, +} from "@termix/plugin-sdk/backend"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import { assertCapability } from "./permissions.js"; +import { getActor } from "./actor.js"; + +type AuditFn = ( + action: string, + details: string, + outcome: { success: boolean; errorMessage?: string }, +) => Promise; + +interface Deps { + manifest: PluginManifest; + audit: AuditFn; +} + +function toSummary(host: { + id: number; + userId: string; + name: string | null; + ip: string; + port: number; + username: string; + tags: string | null; + folder: string | null; + authType: string; +}): PluginHostSummary { + return { + id: host.id, + userId: host.userId, + name: host.name, + ip: host.ip, + port: host.port, + username: host.username, + tags: host.tags, + folder: host.folder, + authType: host.authType, + }; +} + +function actingUser(): string { + const actor = getActor(); + if (!actor) { + throw new Error( + "ctx.hosts needs an acting user: call it inside a request or ctx.asUser", + ); + } + return actor; +} + +export function createPluginHosts({ manifest, audit }: Deps): PluginHosts { + const pluginId = manifest.id; + const declared = manifest.capabilities; + + const requireRead = () => assertCapability(pluginId, "hosts:read", declared); + const requireWrite = () => + assertCapability(pluginId, "hosts:write", declared); + + return { + list: async () => { + await requireRead(); + const userId = actingUser(); + const { + createCurrentHostResolutionRepository, + createCurrentRoleRepository, + createCurrentRbacAccessRepository, + } = await import("../database/repositories/factory.js"); + const repository = createCurrentHostResolutionRepository(); + + const owned = await repository.findHostsByUserId(userId); + const roleIds = + await createCurrentRoleRepository().listUserRoleIds(userId); + const grants = + await createCurrentRbacAccessRepository().listVisibleHostAccessEntries( + userId, + roleIds, + ); + const sharedRows = await repository.listHostRowsForAccessList( + userId, + grants, + ); + + const byId = new Map(); + for (const host of owned) byId.set(host.id, toSummary(host)); + for (const host of sharedRows) { + if (!byId.has(host.id)) byId.set(host.id, toSummary(host)); + } + return [...byId.values()]; + }, + + get: async (hostId) => { + await requireRead(); + const userId = actingUser(); + const { PermissionManager } = + await import("../utils/permission-manager.js"); + const access = await PermissionManager.getInstance().canAccessHost( + userId, + hostId, + "connect", + ); + if (!access.hasAccess) return null; + + const { createCurrentHostResolutionRepository } = + await import("../database/repositories/factory.js"); + const ownerId = + (await createCurrentHostResolutionRepository().findHostOwnerId( + hostId, + )) ?? userId; + const host = await createCurrentHostResolutionRepository().findHostById( + hostId, + ownerId, + ); + return host ? toSummary(host) : null; + }, + + checkAccess: async ( + hostId: number, + level: PluginHostShareLevel, + ): Promise => { + await requireRead(); + const userId = actingUser(); + const { PermissionManager } = + await import("../utils/permission-manager.js"); + const access = await PermissionManager.getInstance().canAccessHost( + userId, + hostId, + level, + ); + return { + hasAccess: access.hasAccess, + isOwner: access.isOwner, + isShared: access.isShared, + permissionLevel: access.permissionLevel as + PluginHostShareLevel | undefined, + expiresAt: access.expiresAt, + }; + }, + + share: async ( + hostId: number, + targets: PluginShareTarget[], + permissionLevel: PluginHostShareLevel, + durationHours?: number, + ): Promise => { + try { + await requireWrite(); + } catch (error) { + await audit("hosts_share", `host ${hostId}`, { + success: false, + errorMessage: error instanceof Error ? error.message : String(error), + }); + throw error; + } + + const userId = actingUser(); + const { PermissionManager } = + await import("../utils/permission-manager.js"); + const access = await PermissionManager.getInstance().canAccessHost( + userId, + hostId, + "manage", + ); + if (!access.hasAccess) { + const result: PluginHostShareResult = { + hostId, + shared: false, + reason: "forbidden", + }; + await audit("hosts_share", `host ${hostId}`, { + success: false, + errorMessage: "forbidden", + }); + return result; + } + + const { + createCurrentHostResolutionRepository, + createCurrentRbacAccessRepository, + } = await import("../database/repositories/factory.js"); + const ownerId = + (await createCurrentHostResolutionRepository().findHostOwnerId( + hostId, + )) ?? userId; + + if (targets.some((t) => t.type === "user" && t.id === ownerId)) { + await audit("hosts_share", `host ${hostId}`, { + success: false, + errorMessage: "owner", + }); + return { hostId, shared: false, reason: "owner" }; + } + + const expiresAt = + durationHours && durationHours > 0 + ? new Date(Date.now() + durationHours * 60 * 60 * 1000).toISOString() + : null; + + const rbacAccessRepository = createCurrentRbacAccessRepository(); + const { SharedHostSecretsManager } = + await import("../utils/shared-host-secrets-manager.js"); + const secretsManager = SharedHostSecretsManager.getInstance(); + + for (const target of targets) { + const grant = await rbacAccessRepository.upsertHostAccess({ + hostId, + grantedBy: userId, + permissionLevel, + expiresAt, + ...(target.type === "user" + ? { targetType: "user" as const, targetUserId: target.id as string } + : { + targetType: "role" as const, + targetRoleId: target.id as number, + }), + }); + + try { + if (target.type === "user") { + await secretsManager.snapshotForUser( + grant.id, + hostId, + target.id as string, + ownerId, + ); + } else { + await secretsManager.snapshotForRole( + grant.id, + hostId, + target.id as number, + ownerId, + ); + } + } catch { + // A snapshot failure never blocks the grant: the recipient can + // still be prompted, or the owner can retry sharing later. + } + } + + await audit("hosts_share", `host ${hostId}`, { success: true }); + return { hostId, shared: true }; + }, + + listUsers: async (): Promise => { + try { + await requireWrite(); + } catch (error) { + await audit("hosts_list_users", "share target picker", { + success: false, + errorMessage: error instanceof Error ? error.message : String(error), + }); + throw error; + } + const { createCurrentUserRepository } = + await import("../database/repositories/factory.js"); + const users = await createCurrentUserRepository().listAll(); + await audit("hosts_list_users", "share target picker", { + success: true, + }); + return users.map((u) => ({ id: u.id, username: u.username })); + }, + + listRoles: async (): Promise => { + try { + await requireWrite(); + } catch (error) { + await audit("hosts_list_roles", "share target picker", { + success: false, + errorMessage: error instanceof Error ? error.message : String(error), + }); + throw error; + } + const { createCurrentRoleRepository } = + await import("../database/repositories/factory.js"); + const roles = await createCurrentRoleRepository().listRoles(); + await audit("hosts_list_roles", "share target picker", { + success: true, + }); + return roles + .filter((r) => !r.isSystem) + .map((r) => ({ id: r.id, name: r.name, displayName: r.displayName })); + }, + }; +} diff --git a/src/backend/plugins/ctx.ts b/src/backend/plugins/ctx.ts index 56723731a..353fb0006 100644 --- a/src/backend/plugins/ctx.ts +++ b/src/backend/plugins/ctx.ts @@ -50,6 +50,7 @@ import { resolveDatabaseDialect, } from "../database/db/dialect.js"; import { createPluginAuth, createPluginSsh } from "./ctx-ssh-auth.js"; +import { createPluginHosts } from "./ctx-hosts.js"; export type { PluginModule }; @@ -608,6 +609,7 @@ export function createPluginContext( }, }, + hosts: createPluginHosts({ manifest, audit: auditCall }), ssh: createPluginSsh({ manifest, bag: handle.bag, audit: auditCall }), auth: createPluginAuth({ manifest, bag: handle.bag, audit: auditCall }), diff --git a/src/backend/tests/plugins/ctx-hosts.test.ts b/src/backend/tests/plugins/ctx-hosts.test.ts new file mode 100644 index 000000000..7161e2b2e --- /dev/null +++ b/src/backend/tests/plugins/ctx-hosts.test.ts @@ -0,0 +1,244 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const h = vi.hoisted(() => ({ + granted: new Set(), + actor: undefined as string | undefined, + access: { + hasAccess: true, + isOwner: true, + isShared: false, + } as Record, + ownedHosts: [] as Array>, + sharedRows: [] as Array>, + visibleGrants: [] as Array<{ hostId: number }>, + ownerId: "user-1", + hostById: null as Record | null, + upserts: [] as Array>, + users: [] as Array<{ id: string; username: string }>, + roles: [] as Array<{ + id: number; + name: string; + displayName: string | null; + isSystem: boolean; + }>, +})); + +vi.mock("../../plugins/permissions.js", async () => { + const { PluginCapabilityError } = await import("@termix/plugin-sdk/backend"); + return { + assertCapability: async ( + pluginId: string, + capability: string, + declared: readonly string[], + ) => { + if (!declared.includes(capability) || !h.granted.has(capability)) { + throw new PluginCapabilityError(pluginId, capability); + } + }, + }; +}); +vi.mock("../../plugins/actor.js", () => ({ getActor: () => h.actor })); +vi.mock("../../utils/permission-manager.js", () => ({ + PermissionManager: { + getInstance: () => ({ + canAccessHost: async () => h.access, + }), + }, +})); +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentHostResolutionRepository: () => ({ + findHostsByUserId: async () => h.ownedHosts, + listHostRowsForAccessList: async () => h.sharedRows, + findHostOwnerId: async () => h.ownerId, + findHostById: async () => h.hostById, + }), + createCurrentRoleRepository: () => ({ + listUserRoleIds: async () => [], + listRoles: async () => h.roles, + }), + createCurrentRbacAccessRepository: () => ({ + listVisibleHostAccessEntries: async () => h.visibleGrants, + upsertHostAccess: async (input: Record) => { + h.upserts.push(input); + return { id: h.upserts.length, created: true }; + }, + }), + createCurrentUserRepository: () => ({ + listAll: async () => h.users, + }), +})); +vi.mock("../../utils/shared-host-secrets-manager.js", () => ({ + SharedHostSecretsManager: { + getInstance: () => ({ + snapshotForUser: async () => {}, + snapshotForRole: async () => {}, + }), + }, +})); + +const { createPluginHosts } = await import("../../plugins/ctx-hosts.js"); +const { PluginCapabilityError } = await import("@termix/plugin-sdk/backend"); + +function manifest(capabilities: string[]) { + return { + id: "fixture", + name: "Fixture", + version: "1.0.0", + capabilities, + } as never; +} + +beforeEach(() => { + h.granted = new Set(); + h.actor = "user-1"; + h.access = { hasAccess: true, isOwner: true, isShared: false }; + h.ownedHosts = []; + h.sharedRows = []; + h.visibleGrants = []; + h.ownerId = "user-1"; + h.hostById = null; + h.upserts = []; + h.users = []; + h.roles = []; +}); + +describe("ctx.hosts", () => { + it("refuses list/get/checkAccess without hosts:read", async () => { + const audit = vi.fn(async () => {}); + const hosts = createPluginHosts({ manifest: manifest([]), audit }); + await expect(hosts.list()).rejects.toBeInstanceOf(PluginCapabilityError); + await expect(hosts.get(1)).rejects.toBeInstanceOf(PluginCapabilityError); + await expect(hosts.checkAccess(1, "view")).rejects.toBeInstanceOf( + PluginCapabilityError, + ); + }); + + it("lists owned and shared hosts once granted hosts:read", async () => { + h.granted = new Set(["hosts:read"]); + h.ownedHosts = [ + { + id: 1, + userId: "user-1", + name: "own", + ip: "10.0.0.1", + port: 22, + username: "root", + tags: "prod", + folder: null, + authType: "password", + }, + ]; + h.sharedRows = [ + { + id: 2, + userId: "user-2", + name: "shared", + ip: "10.0.0.2", + port: 22, + username: "root", + tags: null, + folder: null, + authType: "key", + }, + ]; + const hosts = createPluginHosts({ + manifest: manifest(["hosts:read"]), + audit: vi.fn(async () => {}), + }); + const list = await hosts.list(); + expect(list.map((h2) => h2.id)).toEqual([1, 2]); + }); + + it("checkAccess reports the granted level", async () => { + h.granted = new Set(["hosts:read"]); + h.access = { + hasAccess: true, + isOwner: false, + isShared: true, + permissionLevel: "edit", + expiresAt: null, + }; + const hosts = createPluginHosts({ + manifest: manifest(["hosts:read"]), + audit: vi.fn(async () => {}), + }); + const access = await hosts.checkAccess(1, "edit"); + expect(access).toMatchObject({ hasAccess: true, permissionLevel: "edit" }); + }); + + it("refuses share, listUsers, listRoles without hosts:write, auditing the refusal", async () => { + const audit = vi.fn(async () => {}); + const hosts = createPluginHosts({ manifest: manifest([]), audit }); + await expect( + hosts.share(1, [{ type: "user", id: "user-2" }], "view"), + ).rejects.toBeInstanceOf(PluginCapabilityError); + await expect(hosts.listUsers()).rejects.toBeInstanceOf( + PluginCapabilityError, + ); + await expect(hosts.listRoles()).rejects.toBeInstanceOf( + PluginCapabilityError, + ); + expect(audit).toHaveBeenCalledWith( + "hosts_share", + expect.any(String), + expect.objectContaining({ success: false }), + ); + }); + + it("shares a host it manages and snapshots secrets per target", async () => { + h.granted = new Set(["hosts:write"]); + h.access = { hasAccess: true, isOwner: true, isShared: false }; + const hosts = createPluginHosts({ + manifest: manifest(["hosts:write"]), + audit: vi.fn(async () => {}), + }); + const result = await hosts.share( + 5, + [{ type: "user", id: "user-2" }], + "view", + 24, + ); + expect(result).toEqual({ hostId: 5, shared: true }); + expect(h.upserts).toHaveLength(1); + expect(h.upserts[0]).toMatchObject({ + hostId: 5, + permissionLevel: "view", + targetType: "user", + targetUserId: "user-2", + }); + }); + + it("refuses to share a host the caller does not manage", async () => { + h.granted = new Set(["hosts:write"]); + h.access = { hasAccess: false, isOwner: false, isShared: false }; + const hosts = createPluginHosts({ + manifest: manifest(["hosts:write"]), + audit: vi.fn(async () => {}), + }); + const result = await hosts.share( + 5, + [{ type: "user", id: "user-2" }], + "view", + ); + expect(result).toEqual({ hostId: 5, shared: false, reason: "forbidden" }); + }); + + it("lists non-system roles and users once granted hosts:write", async () => { + h.granted = new Set(["hosts:write"]); + h.users = [{ id: "user-1", username: "alice" }]; + h.roles = [ + { id: 1, name: "admin", displayName: "Admin", isSystem: true }, + { id: 2, name: "auditor", displayName: "Auditor", isSystem: false }, + ]; + const hosts = createPluginHosts({ + manifest: manifest(["hosts:write"]), + audit: vi.fn(async () => {}), + }); + expect(await hosts.listUsers()).toEqual([ + { id: "user-1", username: "alice" }, + ]); + expect(await hosts.listRoles()).toEqual([ + { id: 2, name: "auditor", displayName: "Auditor" }, + ]); + }); +}); diff --git a/src/backend/tests/plugins/host-commands.test.ts b/src/backend/tests/plugins/host-commands.test.ts new file mode 100644 index 000000000..77c95bf8b --- /dev/null +++ b/src/backend/tests/plugins/host-commands.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect } from "vitest"; +import { + parsePlatformProbe, + buildPackageActionCommand, + buildPackageRemoveCommand, + isValidPackageName, + buildSudoCommand, + parseUpgradable, +} from "@termix/plugin-sdk/host-commands"; + +describe("parsePlatformProbe", () => { + it("prefers dnf over yum when both are present", () => { + const out = ["dnf=1", "yum=1", "apt=0", "pacman=0"].join("\n"); + expect(parsePlatformProbe(out).pkg).toBe("dnf"); + }); + + it("reads the pretty OS name and tool flags", () => { + const out = [ + "systemd=1", + "apt=1", + "dnf=0", + "yum=0", + "pacman=0", + "certbot=1", + "acmesh=0", + "docker=1", + "os=Debian GNU/Linux 12 (bookworm)", + ].join("\n"); + const info = parsePlatformProbe(out); + expect(info).toMatchObject({ + hasSystemd: true, + pkg: "apt", + hasCertbot: true, + hasAcmeSh: false, + hasDocker: true, + osPrettyName: "Debian GNU/Linux 12 (bookworm)", + }); + }); + + it("nulls the OS name when the probe line is empty", () => { + expect(parsePlatformProbe("os=").osPrettyName).toBeNull(); + }); +}); + +describe("buildPackageActionCommand", () => { + it("builds install and upgrade-all commands per package manager", () => { + expect(buildPackageActionCommand("apt", "install", "curl")).toContain( + "apt-get -y install curl", + ); + expect(buildPackageActionCommand("dnf", "upgrade-all")).toBe( + "dnf -y upgrade", + ); + expect(buildPackageActionCommand("yum", "install", "curl")).toBe( + "yum -y install curl", + ); + expect(buildPackageActionCommand("pacman", "upgrade-all")).toBe( + "pacman -Syu --noconfirm", + ); + }); + + it("returns null when no package manager was detected", () => { + expect(buildPackageActionCommand(null, "install", "curl")).toBeNull(); + }); +}); + +describe("buildPackageRemoveCommand", () => { + it("builds the correct remove command per package manager", () => { + expect(buildPackageRemoveCommand("apt", "curl")).toContain( + "apt-get -y remove curl", + ); + expect(buildPackageRemoveCommand("dnf", "curl")).toBe("dnf -y remove curl"); + expect(buildPackageRemoveCommand("yum", "curl")).toBe("yum -y remove curl"); + expect(buildPackageRemoveCommand("pacman", "curl")).toBe( + "pacman -R --noconfirm curl", + ); + }); + + it("returns null when no package manager was detected", () => { + expect(buildPackageRemoveCommand(null, "curl")).toBeNull(); + }); +}); + +describe("isValidPackageName", () => { + it("accepts ordinary package names", () => { + expect(isValidPackageName("curl")).toBe(true); + expect(isValidPackageName("lib32-glibc")).toBe(true); + }); + + it("rejects shell metacharacters and empty strings", () => { + expect(isValidPackageName("curl; rm -rf /")).toBe(false); + expect(isValidPackageName("")).toBe(false); + expect(isValidPackageName(123)).toBe(false); + }); +}); + +describe("buildSudoCommand", () => { + it("single-quotes the password and wraps the marker echo around the command", () => { + const cmd = buildSudoCommand("uptime", "p'w"); + expect(cmd).toContain("sudo -S -p ''"); + expect(cmd).toContain("__TX_SUDO_OK__"); + expect(cmd).toContain("uptime"); + }); +}); + +describe("parseUpgradable", () => { + it("parses apt list --upgradable output", () => { + const out = "curl/stable 7.88.0 amd64 [upgradable from: 7.87.0]"; + expect(parseUpgradable("apt", out)).toEqual([ + { name: "curl", newVersion: "7.88.0", currentVersion: "7.87.0" }, + ]); + }); + + it("returns an empty list for no package manager", () => { + expect(parseUpgradable(null, "anything")).toEqual([]); + }); +});