diff --git a/drizzle/mysql/0029_open_captain_america.sql b/drizzle/mysql/0029_open_captain_america.sql new file mode 100644 index 000000000..74e0ef7a4 --- /dev/null +++ b/drizzle/mysql/0029_open_captain_america.sql @@ -0,0 +1,15 @@ +CREATE TABLE `plugin_settings` ( + `id` int AUTO_INCREMENT NOT NULL, + `plugin_id` varchar(255) NOT NULL, + `scope` varchar(255) NOT NULL, + `scope_id` varchar(255), + `key` varchar(255) NOT NULL, + `value` text, + `encrypted` boolean NOT NULL DEFAULT false, + `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP), + CONSTRAINT `plugin_settings_id` PRIMARY KEY(`id`), + CONSTRAINT `idx_plugin_settings_scope_key` UNIQUE(`plugin_id`,`scope`,`scope_id`,`key`) +); +--> statement-breakpoint +ALTER TABLE `plugin_settings` ADD CONSTRAINT `plugin_settings_plugin_id_plugins_id_fk` FOREIGN KEY (`plugin_id`) REFERENCES `plugins`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX `idx_plugin_settings_plugin_scope` ON `plugin_settings` (`plugin_id`,`scope`); \ No newline at end of file diff --git a/drizzle/mysql/meta/0029_snapshot.json b/drizzle/mysql/meta/0029_snapshot.json new file mode 100644 index 000000000..228245fbc --- /dev/null +++ b/drizzle/mysql/meta/0029_snapshot.json @@ -0,0 +1,10062 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "b2e69137-0d9e-4e84-8259-6f104a7ce2fb", + "prevId": "531e612d-b14f-4e1e-9d8c-1f9aa3baf88f", + "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": {} + }, + "fleet_inventory": { + "name": "fleet_inventory", + "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 + }, + "os_pretty_name": { + "name": "os_pretty_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kernel": { + "name": "kernel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "architecture": { + "name": "architecture", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uptime_seconds": { + "name": "uptime_seconds", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "package_manager": { + "name": "package_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "collected_at": { + "name": "collected_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_fleet_inventory_host": { + "name": "idx_fleet_inventory_host", + "columns": [ + "host_id", + "user_id" + ], + "isUnique": true + }, + "idx_fleet_inventory_user": { + "name": "idx_fleet_inventory_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "fleet_inventory_host_id_ssh_data_id_fk": { + "name": "fleet_inventory_host_id_ssh_data_id_fk", + "tableFrom": "fleet_inventory", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fleet_inventory_user_id_users_id_fk": { + "name": "fleet_inventory_user_id_users_id_fk", + "tableFrom": "fleet_inventory", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "fleet_inventory_id": { + "name": "fleet_inventory_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "fleet_members": { + "name": "fleet_members", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "fleet_id": { + "name": "fleet_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "added_at": { + "name": "added_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_fleet_members_fleet_host": { + "name": "idx_fleet_members_fleet_host", + "columns": [ + "fleet_id", + "host_id" + ], + "isUnique": true + }, + "idx_fleet_members_host": { + "name": "idx_fleet_members_host", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "fleet_members_fleet_id_fleets_id_fk": { + "name": "fleet_members_fleet_id_fleets_id_fk", + "tableFrom": "fleet_members", + "tableTo": "fleets", + "columnsFrom": [ + "fleet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fleet_members_host_id_ssh_data_id_fk": { + "name": "fleet_members_host_id_ssh_data_id_fk", + "tableFrom": "fleet_members", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "fleet_members_id": { + "name": "fleet_members_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "fleets": { + "name": "fleets", + "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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tag_rules": { + "name": "tag_rules", + "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": {}, + "foreignKeys": { + "fleets_user_id_users_id_fk": { + "name": "fleets_user_id_users_id_fk", + "tableFrom": "fleets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "fleets_id": { + "name": "fleets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "fleets_sync_id_unique": { + "name": "fleets_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "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": {} + }, + "network_topology": { + "name": "network_topology", + "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 + }, + "topology": { + "name": "topology", + "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": { + "network_topology_user_id_users_id_fk": { + "name": "network_topology_user_id_users_id_fk", + "tableFrom": "network_topology", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "network_topology_id": { + "name": "network_topology_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "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": {} + }, + "snippet_access": { + "name": "snippet_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "snippet_id": { + "name": "snippet_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": "('view')" + }, + "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_snippet_access_user_id": { + "name": "idx_snippet_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_snippet_access_snippet_id": { + "name": "idx_snippet_access_snippet_id", + "columns": [ + "snippet_id" + ], + "isUnique": false + }, + "idx_snippet_access_role_id": { + "name": "idx_snippet_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "snippet_access_snippet_id_snippets_id_fk": { + "name": "snippet_access_snippet_id_snippets_id_fk", + "tableFrom": "snippet_access", + "tableTo": "snippets", + "columnsFrom": [ + "snippet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_user_id_users_id_fk": { + "name": "snippet_access_user_id_users_id_fk", + "tableFrom": "snippet_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_role_id_roles_id_fk": { + "name": "snippet_access_role_id_roles_id_fk", + "tableFrom": "snippet_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_granted_by_users_id_fk": { + "name": "snippet_access_granted_by_users_id_fk", + "tableFrom": "snippet_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "snippet_access_id": { + "name": "snippet_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "snippet_folders": { + "name": "snippet_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 + }, + "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": { + "snippet_folders_user_id_users_id_fk": { + "name": "snippet_folders_user_id_users_id_fk", + "tableFrom": "snippet_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "snippet_folders_id": { + "name": "snippet_folders_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "snippet_folders_sync_id_unique": { + "name": "snippet_folders_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "snippets": { + "name": "snippets", + "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 + }, + "content": { + "name": "content", + "type": "text", + "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 + }, + "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)" + }, + "host_filter": { + "name": "host_filter", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_note": { + "name": "is_note", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_snippets_user_id": { + "name": "idx_snippets_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "snippets_user_id_users_id_fk": { + "name": "snippets_user_id_users_id_fk", + "tableFrom": "snippets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "snippets_id": { + "name": "snippets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "snippets_sync_id_unique": { + "name": "snippets_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "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_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_workspaces": { + "name": "user_workspaces", + "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 + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('manual')" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{}')" + }, + "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)" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_user_workspaces_user_id": { + "name": "idx_user_workspaces_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_workspaces_user_id_users_id_fk": { + "name": "user_workspaces_user_id_users_id_fk", + "tableFrom": "user_workspaces", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_workspaces_id": { + "name": "user_workspaces_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "user_workspaces_sync_id_unique": { + "name": "user_workspaces_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "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 14dc1a628..17a59fb5f 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -204,6 +204,13 @@ "when": 1790114765594, "tag": "0028_fair_tusk", "breakpoints": true + }, + { + "idx": 29, + "version": "5", + "when": 1790118361160, + "tag": "0029_open_captain_america", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0030_cynical_callisto.sql b/drizzle/postgres/0030_cynical_callisto.sql new file mode 100644 index 000000000..fcbfbd1e6 --- /dev/null +++ b/drizzle/postgres/0030_cynical_callisto.sql @@ -0,0 +1,14 @@ +CREATE TABLE "plugin_settings" ( + "id" serial PRIMARY KEY NOT NULL, + "plugin_id" varchar(255) NOT NULL, + "scope" varchar(255) NOT NULL, + "scope_id" varchar(255), + "key" varchar(255) NOT NULL, + "value" text, + "encrypted" boolean DEFAULT false NOT NULL, + "updated_at" varchar(255) DEFAULT CURRENT_TIMESTAMP NOT NULL +); +--> statement-breakpoint +ALTER TABLE "plugin_settings" ADD CONSTRAINT "plugin_settings_plugin_id_plugins_id_fk" FOREIGN KEY ("plugin_id") REFERENCES "public"."plugins"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "idx_plugin_settings_scope_key" ON "plugin_settings" USING btree ("plugin_id","scope","scope_id","key");--> statement-breakpoint +CREATE INDEX "idx_plugin_settings_plugin_scope" ON "plugin_settings" USING btree ("plugin_id","scope"); \ No newline at end of file diff --git a/drizzle/postgres/meta/0030_snapshot.json b/drizzle/postgres/meta/0030_snapshot.json new file mode 100644 index 000000000..7811ddcb6 --- /dev/null +++ b/drizzle/postgres/meta/0030_snapshot.json @@ -0,0 +1,9807 @@ +{ + "id": "cddffc1f-93b9-4d09-ab23-85160ff82a47", + "prevId": "89587b65-e3fc-4b49-8f38-a61b4af84619", + "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.fleet_inventory": { + "name": "fleet_inventory", + "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 + }, + "os_pretty_name": { + "name": "os_pretty_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kernel": { + "name": "kernel", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "architecture": { + "name": "architecture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uptime_seconds": { + "name": "uptime_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_manager": { + "name": "package_manager", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collected_at": { + "name": "collected_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_fleet_inventory_host": { + "name": "idx_fleet_inventory_host", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_fleet_inventory_user": { + "name": "idx_fleet_inventory_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fleet_inventory_host_id_ssh_data_id_fk": { + "name": "fleet_inventory_host_id_ssh_data_id_fk", + "tableFrom": "fleet_inventory", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fleet_inventory_user_id_users_id_fk": { + "name": "fleet_inventory_user_id_users_id_fk", + "tableFrom": "fleet_inventory", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.fleet_members": { + "name": "fleet_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "fleet_id": { + "name": "fleet_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_fleet_members_fleet_host": { + "name": "idx_fleet_members_fleet_host", + "columns": [ + { + "expression": "fleet_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_fleet_members_host": { + "name": "idx_fleet_members_host", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fleet_members_fleet_id_fleets_id_fk": { + "name": "fleet_members_fleet_id_fleets_id_fk", + "tableFrom": "fleet_members", + "tableTo": "fleets", + "columnsFrom": [ + "fleet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fleet_members_host_id_ssh_data_id_fk": { + "name": "fleet_members_host_id_ssh_data_id_fk", + "tableFrom": "fleet_members", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.fleets": { + "name": "fleets", + "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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag_rules": { + "name": "tag_rules", + "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": {}, + "foreignKeys": { + "fleets_user_id_users_id_fk": { + "name": "fleets_user_id_users_id_fk", + "tableFrom": "fleets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "fleets_sync_id_unique": { + "name": "fleets_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "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.network_topology": { + "name": "network_topology", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "topology": { + "name": "topology", + "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": { + "network_topology_user_id_users_id_fk": { + "name": "network_topology_user_id_users_id_fk", + "tableFrom": "network_topology", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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.snippet_access": { + "name": "snippet_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "snippet_id": { + "name": "snippet_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": "'view'" + }, + "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_snippet_access_user_id": { + "name": "idx_snippet_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_snippet_access_snippet_id": { + "name": "idx_snippet_access_snippet_id", + "columns": [ + { + "expression": "snippet_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_snippet_access_role_id": { + "name": "idx_snippet_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "snippet_access_snippet_id_snippets_id_fk": { + "name": "snippet_access_snippet_id_snippets_id_fk", + "tableFrom": "snippet_access", + "tableTo": "snippets", + "columnsFrom": [ + "snippet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_user_id_users_id_fk": { + "name": "snippet_access_user_id_users_id_fk", + "tableFrom": "snippet_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_role_id_roles_id_fk": { + "name": "snippet_access_role_id_roles_id_fk", + "tableFrom": "snippet_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_granted_by_users_id_fk": { + "name": "snippet_access_granted_by_users_id_fk", + "tableFrom": "snippet_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.snippet_folders": { + "name": "snippet_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 + }, + "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": { + "snippet_folders_user_id_users_id_fk": { + "name": "snippet_folders_user_id_users_id_fk", + "tableFrom": "snippet_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "snippet_folders_sync_id_unique": { + "name": "snippet_folders_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.snippets": { + "name": "snippets", + "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 + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "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" + }, + "host_filter": { + "name": "host_filter", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_note": { + "name": "is_note", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_snippets_user_id": { + "name": "idx_snippets_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "snippets_user_id_users_id_fk": { + "name": "snippets_user_id_users_id_fk", + "tableFrom": "snippets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "snippets_sync_id_unique": { + "name": "snippets_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "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_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_workspaces": { + "name": "user_workspaces", + "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 + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "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" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_workspaces_user_id": { + "name": "idx_user_workspaces_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_workspaces_user_id_users_id_fk": { + "name": "user_workspaces_user_id_users_id_fk", + "tableFrom": "user_workspaces", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_workspaces_sync_id_unique": { + "name": "user_workspaces_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "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 58a850144..d1cbcf31c 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -211,6 +211,13 @@ "when": 1790114765114, "tag": "0029_nosy_roulette", "breakpoints": true + }, + { + "idx": 30, + "version": "7", + "when": 1790118360674, + "tag": "0030_cynical_callisto", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0026_fair_sauron.sql b/drizzle/sqlite/0026_fair_sauron.sql new file mode 100644 index 000000000..10caf3169 --- /dev/null +++ b/drizzle/sqlite/0026_fair_sauron.sql @@ -0,0 +1,14 @@ +CREATE TABLE `plugin_settings` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `plugin_id` text NOT NULL, + `scope` text NOT NULL, + `scope_id` text, + `key` text NOT NULL, + `value` text, + `encrypted` integer DEFAULT false NOT NULL, + `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`plugin_id`) REFERENCES `plugins`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `idx_plugin_settings_scope_key` ON `plugin_settings` (`plugin_id`,`scope`,`scope_id`,`key`);--> statement-breakpoint +CREATE INDEX `idx_plugin_settings_plugin_scope` ON `plugin_settings` (`plugin_id`,`scope`); \ No newline at end of file diff --git a/drizzle/sqlite/meta/0026_snapshot.json b/drizzle/sqlite/meta/0026_snapshot.json new file mode 100644 index 000000000..21f91f1b7 --- /dev/null +++ b/drizzle/sqlite/meta/0026_snapshot.json @@ -0,0 +1,9483 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "50b36b42-89b2-4526-aea9-d9c5363cbacd", + "prevId": "39cbfaf1-279f-45dc-8661-69070540e3d2", + "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": {} + }, + "fleet_inventory": { + "name": "fleet_inventory", + "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 + }, + "os_pretty_name": { + "name": "os_pretty_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kernel": { + "name": "kernel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "architecture": { + "name": "architecture", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uptime_seconds": { + "name": "uptime_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "package_manager": { + "name": "package_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "collected_at": { + "name": "collected_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_fleet_inventory_host": { + "name": "idx_fleet_inventory_host", + "columns": [ + "host_id", + "user_id" + ], + "isUnique": true + }, + "idx_fleet_inventory_user": { + "name": "idx_fleet_inventory_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "fleet_inventory_host_id_ssh_data_id_fk": { + "name": "fleet_inventory_host_id_ssh_data_id_fk", + "tableFrom": "fleet_inventory", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fleet_inventory_user_id_users_id_fk": { + "name": "fleet_inventory_user_id_users_id_fk", + "tableFrom": "fleet_inventory", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "fleet_members": { + "name": "fleet_members", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "fleet_id": { + "name": "fleet_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "added_at": { + "name": "added_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_fleet_members_fleet_host": { + "name": "idx_fleet_members_fleet_host", + "columns": [ + "fleet_id", + "host_id" + ], + "isUnique": true + }, + "idx_fleet_members_host": { + "name": "idx_fleet_members_host", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "fleet_members_fleet_id_fleets_id_fk": { + "name": "fleet_members_fleet_id_fleets_id_fk", + "tableFrom": "fleet_members", + "tableTo": "fleets", + "columnsFrom": [ + "fleet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fleet_members_host_id_ssh_data_id_fk": { + "name": "fleet_members_host_id_ssh_data_id_fk", + "tableFrom": "fleet_members", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "fleets": { + "name": "fleets", + "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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tag_rules": { + "name": "tag_rules", + "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": { + "fleets_sync_id_unique": { + "name": "fleets_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "fleets_user_id_users_id_fk": { + "name": "fleets_user_id_users_id_fk", + "tableFrom": "fleets", + "tableTo": "users", + "columnsFrom": [ + "user_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": {} + }, + "network_topology": { + "name": "network_topology", + "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 + }, + "topology": { + "name": "topology", + "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": { + "network_topology_user_id_users_id_fk": { + "name": "network_topology_user_id_users_id_fk", + "tableFrom": "network_topology", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "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": {} + }, + "snippet_access": { + "name": "snippet_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "snippet_id": { + "name": "snippet_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": "'view'" + }, + "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_snippet_access_user_id": { + "name": "idx_snippet_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_snippet_access_snippet_id": { + "name": "idx_snippet_access_snippet_id", + "columns": [ + "snippet_id" + ], + "isUnique": false + }, + "idx_snippet_access_role_id": { + "name": "idx_snippet_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "snippet_access_snippet_id_snippets_id_fk": { + "name": "snippet_access_snippet_id_snippets_id_fk", + "tableFrom": "snippet_access", + "tableTo": "snippets", + "columnsFrom": [ + "snippet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_user_id_users_id_fk": { + "name": "snippet_access_user_id_users_id_fk", + "tableFrom": "snippet_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_role_id_roles_id_fk": { + "name": "snippet_access_role_id_roles_id_fk", + "tableFrom": "snippet_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "snippet_access_granted_by_users_id_fk": { + "name": "snippet_access_granted_by_users_id_fk", + "tableFrom": "snippet_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "snippet_folders": { + "name": "snippet_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 + }, + "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": { + "snippet_folders_sync_id_unique": { + "name": "snippet_folders_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "snippet_folders_user_id_users_id_fk": { + "name": "snippet_folders_user_id_users_id_fk", + "tableFrom": "snippet_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "snippets": { + "name": "snippets", + "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 + }, + "content": { + "name": "content", + "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 + }, + "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" + }, + "host_filter": { + "name": "host_filter", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_note": { + "name": "is_note", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "snippets_sync_id_unique": { + "name": "snippets_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_snippets_user_id": { + "name": "idx_snippets_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "snippets_user_id_users_id_fk": { + "name": "snippets_user_id_users_id_fk", + "tableFrom": "snippets", + "tableTo": "users", + "columnsFrom": [ + "user_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_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_workspaces": { + "name": "user_workspaces", + "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 + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "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" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_workspaces_sync_id_unique": { + "name": "user_workspaces_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_user_workspaces_user_id": { + "name": "idx_user_workspaces_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_workspaces_user_id_users_id_fk": { + "name": "user_workspaces_user_id_users_id_fk", + "tableFrom": "user_workspaces", + "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 907dec9e6..3d2bb5521 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1790114764617, "tag": "0025_overrated_cable", "breakpoints": true + }, + { + "idx": 26, + "version": "6", + "when": 1790118360159, + "tag": "0026_fair_sauron", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/plugin-sdk/ARCHITECTURE.md b/packages/plugin-sdk/ARCHITECTURE.md index ff123eed5..23bbf88d1 100644 --- a/packages/plugin-sdk/ARCHITECTURE.md +++ b/packages/plugin-sdk/ARCHITECTURE.md @@ -32,6 +32,7 @@ dependencies. Entry points: | `@termix/plugin-sdk/frontend` | The `app` object types (stubs until A7) | | `@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, incl. `createFakeContext()` | Plugins never import from `src/` or `@/`, and core never imports from @@ -205,10 +206,68 @@ by an authenticated route). ### 7. Settings Plugins declare settings fields in the manifest with scope `admin`, `user` or -`host`. Core renders them from the schema (with an optional custom component), -stores values in `plugin_settings`, and encrypts secret fields. One Settings -screen has a Plugins section with a page per plugin; host settings render in -the host editor. **A6.** +`host`. Core renders them from the schema, stores values in `plugin_settings`, +and encrypts secret fields. + +**A6 delivered this.** A field is `{ key, type, labelKey, ... }`, with types +`boolean`, `string`, `number`, `select`, `multiselect`, `secret`, `textarea`, +`json` and `custom`. `requires` names a boolean in the same scope that gates +it, `group` is a section heading, and `permission` is who may write it: admin +fields fall back to `admin.plugins.manage`, and a short name resolves against +the plugin's own permissions exactly as `ctx.rbac` does. + +`type: "custom"` names a component the frontend registered, for the few things +a schema cannot express (a device browser, a provider list). It is deliberately +narrow: a plugin supplies data everywhere else, so it cannot ship its own form +styling and drift from the rest of the app, and a page disappears cleanly when +its plugin does. + +**One service, two callers.** `src/backend/plugins/settings.ts` is behind both +`ctx.settings` and the HTTP routes, so validation, encryption and redaction are +decided once. Two implementations would drift, and the one that drifted would +be the one writing to the database. + +**The manifest is the schema.** A read is driven by the declared fields, not by +the stored rows: an unwritten field comes back with its `default`, and a row +for a field the plugin has since dropped is ignored rather than handed to the +UI. A write to a key the manifest never declared is refused, which is what +stops a PUT from filling a plugin's namespace with arbitrary keys. + +**Secrets never leave the server.** A `secret` field is encrypted with +`encryptSystemSecret` (the installation key, not a user DEK: these have to be +readable before any user is unlocked) and every HTTP read replaces it with +`{ set: boolean }`. Sending that marker back is a no-op, so saving a form +cannot clear a key it was never shown. Clearing one writes null rather than an +encrypted empty string, so it reads back as unset. + +**Storage.** `plugin_settings(plugin_id, scope, scope_id, key, value, +encrypted, updated_at)`, unique on the first four. `scope_id` is null for +admin, a user id for user and a host id as text for host. It is polymorphic, so +it carries no foreign key and the engine cannot cascade it: `UserRepository` +and `HostRepository` delete these rows explicitly, with tests that fail if +those calls ever go. The foreign key to `plugins` is real, so uninstalling +leaves nothing. + +**Routes**, all with core auth: `GET|PUT /plugins/:id/settings/admin` +(`admin.plugins.manage`, or the field's own permission), `/settings/user` +(always the caller, never a user id from the body) and +`/settings/host/:hostId` (needs edit access to that host). A rejected PUT +returns `400 { errors: { : message } }` per field, so a form shows every +problem at once. + +**Host payloads carry them.** Host list and get responses include a +`pluginSettings` map, enabled plugins only and secrets redacted, built in one +query for the whole list rather than one request per plugin per host. It is +attached after the sanitizers, because the connect-level projection of a shared +host reduces it to an allowlist. + +`ctx.settings` is `get/set`, `getUser/setUser`, `getHost/setHost`, `getAll`, +`onChange` (disposed with the plugin) and `readCore`. **Reading and writing a +plugin's own settings needs no capability**: the manifest already declares +every field, and a plugin that had to ask permission to read its own +configuration would be useless. Only `readCore`, which reaches outside the +plugin's namespace, is gated on `settings:read-core`, and it serves a short +allowlist (`CORE_SETTINGS_ALLOWLIST`) rather than the whole settings table. ### 8. UI @@ -296,6 +355,32 @@ plugins// Every move of data, settings or host columns into a plugin comes with a migration for all three dialects and a test proving existing data survives. +#### Moving a host column into a plugin + +A6 built the host-scope settings path but moved no columns; each Phase B step +moves its own. `ssh_data` still holds `enable_docker`, `stats_config`, +`web_ui_config`, `enable_proxmox`, `enable_rdp`, `mac_address` and the rest, +and the hardcoded host editor tabs still read them. The order matters, because +dropping a column before its data has moved loses it: + +1. Declare the fields in the plugin's `contributes.settings.host`, keeping the + key names the form already uses so the editor tab can be deleted rather than + rewritten. +2. Write a boot migration that copies each row's column into host-scope + `plugin_settings` for that plugin, keyed by the host id as text. Make it + idempotent by checking for an existing row first, the way + `tailscale-settings-migration.ts` does, and cover it with a test that runs + it twice. +3. Switch the plugin's backend to `ctx.settings.getHost` and delete the + hardcoded editor tab; the schema-driven Plugins group replaces it. +4. Only then drop the column, in lockstep across all four artifacts: the + `schema.ts` declaration, the `db/index.ts` bootstrap DDL, a drizzle + migration per dialect, and `npm run schema:generate` for the pg and mysql + variants. The two bootstrap guard tests fail if these disagree. + +A column that is still read anywhere in core is not ready to be dropped. Ship +the copy and the switch first, and drop it in a later step if that is safer. + --- ## Manifest v2 reference @@ -370,6 +455,52 @@ not do. "defaultRoles": ["user"], // system roles only, applied once }, ], + "settings": { + "admin": [ + { + "key": "apiKey", + "type": "secret", // encrypted at rest, never sent to a browser + "labelKey": "settings.apiKey.label", + "descriptionKey": "settings.apiKey.description", + "placeholderKey": "settings.apiKey.placeholder", + "group": "settings.group.api", // section heading + }, + { + "key": "retries", + "type": "number", + "labelKey": "k", + "min": 0, + "max": 5, + "default": 3, + }, + { + "key": "browser", + "type": "custom", // a registered component, for what a schema cannot express + "component": "devices", + }, + ], + "user": [ + { + "key": "advanced", + "type": "boolean", + "labelKey": "k", + "permission": "tweak", // one of this plugin's own permissions + }, + ], + "host": { + "enableKey": "enableExample", // boolean rendered first, gating the rest + "enableLabelKey": "k", + "fields": [ + { + "key": "port", + "type": "number", + "labelKey": "k", + "requires": "enableExample", + }, + ], + }, + }, + "hostCapability": { "key": "enableExample", "labelKey": "k", @@ -389,6 +520,13 @@ Networking, Access & Security, Productivity. `openFrom` is one of: rail, host-context-menu, palette. +Field types are `boolean`, `string`, `number`, `select`, `multiselect`, +`secret`, `textarea`, `json` and `custom`. `labelKey` is required for every +type but `custom`, which needs `component` instead; `options` is required and +non-empty for `select` and `multiselect` and rejected elsewhere; `min`/`max` +belong to `number` alone; and `requires` must name a boolean field in the same +scope, which includes the host section's `enableKey`. + Cross-field rules the JSON schema cannot express, checked by `parseManifest`: - `engine.api` must match the SDK major version this build implements. @@ -403,6 +541,9 @@ Cross-field rules the JSON schema cannot express, checked by `parseManifest`: - Every `contributes.permissions[].name` must be unique, must not start with a core group (`hosts`, `snippets`, `credentials`, `admin`) or with the plugin's own id, and every `defaultRoles` entry must be `admin` or `user`. +- A `contributes.settings` field `permission` written as a bare short name must + appear in the plugin's own `contributes.permissions`. A dotted id is taken as + given, because the validator cannot know which other plugins exist. `contributes.hostCapability`, `provides`, `requires`, `providesSecret` and `requiresSecret` are carried forward from v1 unchanged because the service and @@ -469,28 +610,28 @@ deciding, not the mechanism. Built per plugin in `src/backend/plugins/ctx.ts` and passed to `activate`. -| Member | Capability | Status | -| ------------------------------------------------ | -------------------------------- | ------ | -| `ctx.pluginId`, `ctx.manifest` | none | **A1** | -| `ctx.log.*` | none | **A1** | -| `ctx.events.emit` / `.on` | `events:core` for core topics | **A1** | -| `ctx.kv.get/set/delete/list` | `kv:own` | **A1** | -| `ctx.registry.*` | none | **A1** | -| `ctx.services.provide` / `.get` | per-service RBAC | **A1** | -| `ctx.secrets.offer` / `.withdraw` / `.getShared` | per-secret RBAC | **A1** | -| `ctx.disposables.add` | none | **A1** | -| `ctx.asUser(userId, fn)` | none, always audited | **A1** | -| `ctx.currentActor()` | none | **A1** | -| `ctx.db.define` / `.client` / `.refs` | `db:own` | **A3** | -| `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.ssh.*` | `ssh:connect`, `credentials:use` | B | -| `ctx.settings.*` | `settings:read-core` | A6 | -| `ctx.notify.*` | `notify:send` | A6 | -| `ctx.auth.*` | `auth:provide` | A8 | -| `ctx.fetch` | `network:outbound` | B | +| Member | Capability | Status | +| ------------------------------------------------ | ------------------------------------ | ------ | +| `ctx.pluginId`, `ctx.manifest` | none | **A1** | +| `ctx.log.*` | none | **A1** | +| `ctx.events.emit` / `.on` | `events:core` for core topics | **A1** | +| `ctx.kv.get/set/delete/list` | `kv:own` | **A1** | +| `ctx.registry.*` | none | **A1** | +| `ctx.services.provide` / `.get` | per-service RBAC | **A1** | +| `ctx.secrets.offer` / `.withdraw` / `.getShared` | per-secret RBAC | **A1** | +| `ctx.disposables.add` | none | **A1** | +| `ctx.asUser(userId, fn)` | none, always audited | **A1** | +| `ctx.currentActor()` | none | **A1** | +| `ctx.db.define` / `.client` / `.refs` | `db:own` | **A3** | +| `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.ssh.*` | `ssh:connect`, `credentials:use` | B | +| `ctx.settings.*` | `settings:read-core` (readCore only) | **A6** | +| `ctx.notify.*` | `notify:send` | A6 | +| `ctx.auth.*` | `auth:provide` | A8 | +| `ctx.fetch` | `network:outbound` | B | ### The actor diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index c45a1090b..810d2bd75 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -32,6 +32,10 @@ "types": "./dist/capabilities.d.ts", "default": "./dist/capabilities.js" }, + "./settings": { + "types": "./dist/settings.d.ts", + "default": "./dist/settings.js" + }, "./testing": { "types": "./dist/testing.d.ts", "default": "./dist/testing.js" diff --git a/packages/plugin-sdk/schema/manifest.schema.json b/packages/plugin-sdk/schema/manifest.schema.json index 08e77f498..23e7bd69b 100644 --- a/packages/plugin-sdk/schema/manifest.schema.json +++ b/packages/plugin-sdk/schema/manifest.schema.json @@ -21,24 +21,42 @@ "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "Unique plugin id. Lowercase, starts with a letter, 2-40 characters. Must equal the plugin's directory name." }, - "name": { "type": "string", "minLength": 1 }, + "name": { + "type": "string", + "minLength": 1 + }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z-.]+)?(\\+[0-9A-Za-z-.]+)?$", "description": "Semver version string." }, - "description": { "type": "string", "minLength": 1 }, + "description": { + "type": "string", + "minLength": 1 + }, "author": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { - "name": { "type": "string", "minLength": 1 }, - "url": { "type": "string", "minLength": 1 } + "name": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "minLength": 1 + } } }, - "license": { "type": "string", "minLength": 1 }, - "repository": { "type": "string", "minLength": 1 }, + "license": { + "type": "string", + "minLength": 1 + }, + "repository": { + "type": "string", + "minLength": 1 + }, "category": { "type": "string", "enum": [ @@ -106,14 +124,24 @@ "dependencies": { "type": "object", "description": "Plugin ids this plugin cannot run without, mapped to a semver range.", - "additionalProperties": { "type": "string", "minLength": 1 }, - "propertyNames": { "pattern": "^[a-z][a-z0-9-]{1,39}$" } + "additionalProperties": { + "type": "string", + "minLength": 1 + }, + "propertyNames": { + "pattern": "^[a-z][a-z0-9-]{1,39}$" + } }, "optionalDependencies": { "type": "object", "description": "Plugin ids this plugin uses when present. It must keep working without them.", - "additionalProperties": { "type": "string", "minLength": 1 }, - "propertyNames": { "pattern": "^[a-z][a-z0-9-]{1,39}$" } + "additionalProperties": { + "type": "string", + "minLength": 1 + }, + "propertyNames": { + "pattern": "^[a-z][a-z0-9-]{1,39}$" + } }, "provides": { "type": "array", @@ -130,7 +158,10 @@ "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z-.]+)?(\\+[0-9A-Za-z-.]+)?$" }, - "permission": { "type": "string", "minLength": 1 } + "permission": { + "type": "string", + "minLength": 1 + } } } }, @@ -145,8 +176,13 @@ "type": "string", "pattern": "^[a-z0-9-]+(\\.[a-z0-9-]+)+$" }, - "versionRange": { "type": "string", "minLength": 1 }, - "optional": { "type": "boolean" } + "versionRange": { + "type": "string", + "minLength": 1 + }, + "optional": { + "type": "boolean" + } } } }, @@ -157,9 +193,18 @@ "additionalProperties": false, "required": ["key", "permission"], "properties": { - "key": { "type": "string", "pattern": "^[a-z0-9-]+$" }, - "permission": { "type": "string", "minLength": 1 }, - "descriptionKey": { "type": "string", "minLength": 1 } + "key": { + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "permission": { + "type": "string", + "minLength": 1 + }, + "descriptionKey": { + "type": "string", + "minLength": 1 + } } } }, @@ -170,9 +215,17 @@ "additionalProperties": false, "required": ["plugin", "key"], "properties": { - "plugin": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" }, - "key": { "type": "string", "pattern": "^[a-z0-9-]+$" }, - "optional": { "type": "boolean" } + "plugin": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]{1,39}$" + }, + "key": { + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "optional": { + "type": "boolean" + } } } }, @@ -187,9 +240,18 @@ "additionalProperties": false, "required": ["id", "titleKey", "icon", "openFrom"], "properties": { - "id": { "type": "string", "minLength": 1 }, - "titleKey": { "type": "string", "minLength": 1 }, - "icon": { "type": "string", "minLength": 1 }, + "id": { + "type": "string", + "minLength": 1 + }, + "titleKey": { + "type": "string", + "minLength": 1 + }, + "icon": { + "type": "string", + "minLength": 1 + }, "openFrom": { "type": "array", "minItems": 1, @@ -212,15 +274,30 @@ "type": "string", "pattern": "^[a-z0-9-]+(\\.[a-zA-Z0-9-]+)+$" }, - "titleKey": { "type": "string", "minLength": 1 }, + "titleKey": { + "type": "string", + "minLength": 1 + }, "handler": { "type": "string", "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$" }, - "icon": { "type": "string", "minLength": 1 }, - "permission": { "type": "string", "minLength": 1 }, - "slot": { "type": "string", "minLength": 1 }, - "kind": { "type": "string", "enum": ["button"] } + "icon": { + "type": "string", + "minLength": 1 + }, + "permission": { + "type": "string", + "minLength": 1 + }, + "slot": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": ["button"] + } } } }, @@ -238,9 +315,15 @@ "accepts": { "type": "array", "minItems": 1, - "items": { "type": "string", "enum": ["button"] } + "items": { + "type": "string", + "enum": ["button"] + } }, - "descriptionKey": { "type": "string", "minLength": 1 } + "descriptionKey": { + "type": "string", + "minLength": 1 + } } } }, @@ -257,12 +340,320 @@ "description": "Plugin-relative name. May contain dots. Must not start with a core group or this plugin's id.", "pattern": "^[a-z0-9_-]+(\\.[a-z0-9_-]+)*$" }, - "titleKey": { "type": "string", "minLength": 1 }, - "descriptionKey": { "type": "string", "minLength": 1 }, + "titleKey": { + "type": "string", + "minLength": 1 + }, + "descriptionKey": { + "type": "string", + "minLength": 1 + }, "defaultRoles": { "type": "array", "description": "System roles that hold this the first time it is seen.", - "items": { "type": "string", "enum": ["admin", "user"] } + "items": { + "type": "string", + "enum": ["admin", "user"] + } + } + } + } + }, + "settings": { + "type": "object", + "description": "Settings fields core renders on the plugin's settings page and in the host editor.", + "additionalProperties": false, + "properties": { + "admin": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["key", "type"], + "properties": { + "key": { + "type": "string", + "description": "Storage key, unique within its scope.", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$" + }, + "type": { + "type": "string", + "enum": [ + "boolean", + "string", + "number", + "select", + "multiselect", + "secret", + "textarea", + "json", + "custom" + ] + }, + "labelKey": { + "type": "string", + "minLength": 1, + "description": "Required for every type except \"custom\"." + }, + "descriptionKey": { + "type": "string", + "minLength": 1 + }, + "placeholderKey": { + "type": "string", + "minLength": 1 + }, + "default": { + "description": "Value used until one is written." + }, + "options": { + "type": "array", + "description": "select and multiselect only.", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["value", "labelKey"], + "properties": { + "value": { + "type": "string", + "minLength": 1 + }, + "labelKey": { + "type": "string", + "minLength": 1 + } + } + } + }, + "min": { + "type": "number", + "description": "number fields only." + }, + "max": { + "type": "number", + "description": "number fields only." + }, + "requires": { + "type": "string", + "description": "Key of a boolean field in the same scope that must be on." + }, + "permission": { + "type": "string", + "minLength": 1, + "description": "Permission needed to write it. Admin fields default to admin.plugins.manage." + }, + "group": { + "type": "string", + "minLength": 1, + "description": "Section heading this field sits under." + }, + "component": { + "type": "string", + "minLength": 1, + "description": "Registered component id. Required when type is \"custom\"." + } + } + } + }, + "user": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["key", "type"], + "properties": { + "key": { + "type": "string", + "description": "Storage key, unique within its scope.", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$" + }, + "type": { + "type": "string", + "enum": [ + "boolean", + "string", + "number", + "select", + "multiselect", + "secret", + "textarea", + "json", + "custom" + ] + }, + "labelKey": { + "type": "string", + "minLength": 1, + "description": "Required for every type except \"custom\"." + }, + "descriptionKey": { + "type": "string", + "minLength": 1 + }, + "placeholderKey": { + "type": "string", + "minLength": 1 + }, + "default": { + "description": "Value used until one is written." + }, + "options": { + "type": "array", + "description": "select and multiselect only.", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["value", "labelKey"], + "properties": { + "value": { + "type": "string", + "minLength": 1 + }, + "labelKey": { + "type": "string", + "minLength": 1 + } + } + } + }, + "min": { + "type": "number", + "description": "number fields only." + }, + "max": { + "type": "number", + "description": "number fields only." + }, + "requires": { + "type": "string", + "description": "Key of a boolean field in the same scope that must be on." + }, + "permission": { + "type": "string", + "minLength": 1, + "description": "Permission needed to write it. Admin fields default to admin.plugins.manage." + }, + "group": { + "type": "string", + "minLength": 1, + "description": "Section heading this field sits under." + }, + "component": { + "type": "string", + "minLength": 1, + "description": "Registered component id. Required when type is \"custom\"." + } + } + } + }, + "host": { + "type": "object", + "additionalProperties": false, + "required": ["fields"], + "properties": { + "enableKey": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$", + "description": "Boolean rendered first, gating the rest of the section." + }, + "enableLabelKey": { + "type": "string", + "minLength": 1 + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["key", "type"], + "properties": { + "key": { + "type": "string", + "description": "Storage key, unique within its scope.", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$" + }, + "type": { + "type": "string", + "enum": [ + "boolean", + "string", + "number", + "select", + "multiselect", + "secret", + "textarea", + "json", + "custom" + ] + }, + "labelKey": { + "type": "string", + "minLength": 1, + "description": "Required for every type except \"custom\"." + }, + "descriptionKey": { + "type": "string", + "minLength": 1 + }, + "placeholderKey": { + "type": "string", + "minLength": 1 + }, + "default": { + "description": "Value used until one is written." + }, + "options": { + "type": "array", + "description": "select and multiselect only.", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["value", "labelKey"], + "properties": { + "value": { + "type": "string", + "minLength": 1 + }, + "labelKey": { + "type": "string", + "minLength": 1 + } + } + } + }, + "min": { + "type": "number", + "description": "number fields only." + }, + "max": { + "type": "number", + "description": "number fields only." + }, + "requires": { + "type": "string", + "description": "Key of a boolean field in the same scope that must be on." + }, + "permission": { + "type": "string", + "minLength": 1, + "description": "Permission needed to write it. Admin fields default to admin.plugins.manage." + }, + "group": { + "type": "string", + "minLength": 1, + "description": "Section heading this field sits under." + }, + "component": { + "type": "string", + "minLength": 1, + "description": "Registered component id. Required when type is \"custom\"." + } + } + } + } } } } @@ -270,11 +661,15 @@ "hostCapability": { "description": "A host-editor checkbox backed by a boolean column on the host record. Reshaped by a later step.", "oneOf": [ - { "$ref": "#/$defs/hostCapability" }, + { + "$ref": "#/$defs/hostCapability" + }, { "type": "array", "minItems": 1, - "items": { "$ref": "#/$defs/hostCapability" } + "items": { + "$ref": "#/$defs/hostCapability" + } } ] } @@ -298,7 +693,10 @@ "platforms": { "type": "array", "minItems": 1, - "items": { "type": "string", "enum": ["linux", "win32", "darwin"] } + "items": { + "type": "string", + "enum": ["linux", "win32", "darwin"] + } } }, "$defs": { @@ -307,9 +705,18 @@ "additionalProperties": false, "required": ["key", "labelKey", "editorTab"], "properties": { - "key": { "type": "string", "minLength": 1 }, - "labelKey": { "type": "string", "minLength": 1 }, - "editorTab": { "type": "string", "minLength": 1 } + "key": { + "type": "string", + "minLength": 1 + }, + "labelKey": { + "type": "string", + "minLength": 1 + }, + "editorTab": { + "type": "string", + "minLength": 1 + } } } } diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index 52f8cfcfa..2a3c1b391 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -297,6 +297,58 @@ export interface PluginDisposables { add: (dispose: () => void | Promise) => void; } +/** + * The plugin's own settings, as declared in contributes.settings. + * + * Reading and writing a plugin's own settings needs no capability: the + * manifest already says which fields exist, and a plugin that could not read + * its own configuration would be useless. Only readCore, which reaches outside + * the plugin's namespace, is gated on settings:read-core. + * + * Values are validated against the declared field on every write, so a key the + * manifest never declared is rejected rather than stored. Secret fields are + * encrypted at rest and returned decrypted here; the HTTP surface never sends + * them to a browser. + */ +export interface PluginSettings { + /** Install-wide admin settings. */ + get: (key: string) => Promise; + set: (key: string, value: unknown) => Promise; + + /** Per-user settings. */ + getUser: (userId: string, key: string) => Promise; + setUser: (userId: string, key: string, value: unknown) => Promise; + + /** Per-host settings. */ + getHost: ( + hostId: number | string, + key: string, + ) => Promise; + setHost: ( + hostId: number | string, + key: string, + value: unknown, + ) => Promise; + + /** Every value in one scope, with declared defaults merged in. */ + getAll: ( + scope: "admin" | "user" | "host", + scopeId?: string | number, + ) => Promise>; + + /** + * Fires when a key in this plugin's settings changes, from either the ctx + * API or the HTTP routes. Disposed automatically on deactivate. + */ + onChange: (key: string, listener: (value: unknown) => void) => () => void; + + /** + * Reads a core server setting from a small documented allowlist. + * Requires the settings:read-core capability. + */ + readCore: (key: string) => Promise; +} + export interface PluginContext { readonly pluginId: string; readonly manifest: PluginManifest; @@ -311,6 +363,7 @@ export interface PluginContext { readonly http: PluginHttp; readonly ws: PluginWebSockets; readonly rbac: PluginRbac; + readonly settings: PluginSettings; readonly disposables: PluginDisposables; /** @@ -347,3 +400,8 @@ export function definePlugin(plugin: PluginModule): PluginModule { export type { PluginManifest } from "./manifest.js"; export type { PluginTableDefinition } from "./db.js"; +export type { + PluginSettingsField, + PluginSettingsContribution, + PluginSettingsScope, +} from "./manifest.js"; diff --git a/packages/plugin-sdk/src/manifest.ts b/packages/plugin-sdk/src/manifest.ts index cfa2299c8..727603721 100644 --- a/packages/plugin-sdk/src/manifest.ts +++ b/packages/plugin-sdk/src/manifest.ts @@ -154,11 +154,80 @@ export interface PluginSecretRequire { optional?: boolean; } +export const SETTINGS_FIELD_TYPES = [ + "boolean", + "string", + "number", + "select", + "multiselect", + "secret", + "textarea", + "json", + "custom", +] as const; +export type PluginSettingsFieldType = (typeof SETTINGS_FIELD_TYPES)[number]; + +export const SETTINGS_SCOPES = ["admin", "user", "host"] as const; +export type PluginSettingsScope = (typeof SETTINGS_SCOPES)[number]; + +export interface PluginSettingsOption { + value: string; + labelKey: string; +} + +/** + * One field on a plugin's settings page. + * + * Core renders these; a plugin supplies data only, so it cannot ship its own + * form styling and drift from the rest of the app. `type: "custom"` is the + * escape hatch for UI a schema cannot express, and names a component the + * frontend registered rather than carrying markup. + */ +export interface PluginSettingsField { + key: string; + type: PluginSettingsFieldType; + /** Required except for "custom", which draws its own label. */ + labelKey?: string; + descriptionKey?: string; + placeholderKey?: string; + default?: unknown; + /** select and multiselect only. */ + options?: PluginSettingsOption[]; + /** number only. */ + min?: number; + max?: number; + /** Key of a boolean field in the same scope that must be on. */ + requires?: string; + /** + * Who may write it. Admin fields default to admin.plugins.manage; a short + * name resolves against this plugin's own permissions. + */ + permission?: string; + /** Section heading this field sits under. */ + group?: string; + /** Registered component id. Required when type is "custom". */ + component?: string; +} + +export interface PluginHostSettingsContribution { + /** Boolean field rendered first, gating the rest of the section. */ + enableKey?: string; + enableLabelKey?: string; + fields: PluginSettingsField[]; +} + +export interface PluginSettingsContribution { + admin?: PluginSettingsField[]; + user?: PluginSettingsField[]; + host?: PluginHostSettingsContribution; +} + export interface PluginContributions { tabs?: PluginTabContribution[]; actions?: PluginActionContribution[]; actionSlots?: PluginActionSlot[]; permissions?: PluginPermissionContribution[]; + settings?: PluginSettingsContribution; /** * A host-editor checkbox backed by a boolean column on the host record. * A5/A6/A7 reshape this; it stays in v2 because remote-desktop, docker, @@ -236,9 +305,29 @@ const ALLOWED_CONTRIBUTES = new Set([ "actions", "actionSlots", "permissions", + "settings", "hostCapability", ]); +const ALLOWED_SETTINGS_FIELD = [ + "key", + "type", + "labelKey", + "descriptionKey", + "placeholderKey", + "default", + "options", + "min", + "max", + "requires", + "permission", + "group", + "component", +]; + +/** Settings keys are stored as-is, so they stay short and index-safe. */ +const SETTINGS_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/; + function isPlainObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } @@ -564,9 +653,201 @@ function validateContributes( validatePermissions(contributes.permissions, pluginId, errors); validateActions(contributes.actions, errors); validateActionSlots(contributes.actionSlots, errors); + validateSettings(contributes.settings, errors); validateHostCapability(contributes.hostCapability, errors); } +function validateSettings(settings: unknown, errors: string[]): void { + if (settings === undefined) return; + const where = "contributes.settings"; + if (!isPlainObject(settings)) { + errors.push(`${where} must be an object`); + return; + } + rejectUnknown(settings, ["admin", "user", "host"], `"${where}"`, errors); + + for (const scope of ["admin", "user"] as const) { + const value = settings[scope]; + if (value === undefined) continue; + if (!Array.isArray(value)) { + errors.push(`${where}.${scope} must be an array of fields`); + continue; + } + validateSettingsFields(value, `${where}.${scope}`, errors); + } + + if (settings.host !== undefined) { + const host = settings.host; + const at = `${where}.host`; + if (!isPlainObject(host)) { + errors.push(`${at} must be an object`); + return; + } + rejectUnknown( + host, + ["enableKey", "enableLabelKey", "fields"], + `"${at}"`, + errors, + ); + + if ("enableKey" in host) { + if ( + typeof host.enableKey !== "string" || + !SETTINGS_KEY_PATTERN.test(host.enableKey) + ) { + errors.push(`${at}.enableKey must be a short alphanumeric key`); + } + // An enable switch with no label is a blank row in the host editor. + requireString(host.enableLabelKey, `${at}.enableLabelKey`, errors); + } + + if (!Array.isArray(host.fields)) { + errors.push(`${at}.fields must be an array`); + return; + } + validateSettingsFields( + host.fields, + `${at}.fields`, + errors, + typeof host.enableKey === "string" ? host.enableKey : undefined, + ); + } +} + +/** + * Validates one scope's fields. + * + * `requires` is checked against the keys declared in the same scope, plus the + * host scope's enableKey, because a field pointing at a key that does not + * exist would simply never render. + */ +function validateSettingsFields( + fields: unknown[], + where: string, + errors: string[], + enableKey?: string, +): void { + const seen = new Set(); + const booleanKeys = new Set(); + if (enableKey) booleanKeys.add(enableKey); + + fields.forEach((raw, index) => { + const at = `${where}[${index}]`; + if (!isPlainObject(raw)) { + errors.push(`${at} must be an object`); + return; + } + rejectUnknown(raw, ALLOWED_SETTINGS_FIELD, at, errors); + + if (typeof raw.key !== "string" || !SETTINGS_KEY_PATTERN.test(raw.key)) { + errors.push(`${at}.key must be a short alphanumeric key`); + } else { + if (seen.has(raw.key)) { + errors.push(`${at}.key duplicates "${raw.key}"`); + } + seen.add(raw.key); + if (raw.type === "boolean") booleanKeys.add(raw.key); + if (enableKey && raw.key === enableKey) { + errors.push( + `${at}.key "${raw.key}" is already the section's enableKey`, + ); + } + } + + if ( + typeof raw.type !== "string" || + !(SETTINGS_FIELD_TYPES as readonly string[]).includes(raw.type) + ) { + errors.push( + `${at}.type must be one of: ${SETTINGS_FIELD_TYPES.join(", ")}`, + ); + return; + } + + // A custom field draws its own label, so it needs a component instead. + if (raw.type === "custom") { + requireString(raw.component, `${at}.component`, errors); + } else { + requireString(raw.labelKey, `${at}.labelKey`, errors); + if ("component" in raw) { + errors.push(`${at}.component is only valid when type is "custom"`); + } + } + + for (const key of ["descriptionKey", "placeholderKey", "group"] as const) { + if (key in raw) requireString(raw[key], `${at}.${key}`, errors); + } + if ("permission" in raw) { + requireString(raw.permission, `${at}.permission`, errors); + } + + if (raw.type === "select" || raw.type === "multiselect") { + validateSettingsOptions(raw.options, at, errors); + } else if ("options" in raw) { + errors.push(`${at}.options is only valid for select and multiselect`); + } + + if (raw.type === "number") { + for (const bound of ["min", "max"] as const) { + if (bound in raw && typeof raw[bound] !== "number") { + errors.push(`${at}.${bound} must be a number`); + } + } + if ( + typeof raw.min === "number" && + typeof raw.max === "number" && + raw.min > raw.max + ) { + errors.push(`${at}.min must not be greater than ${at}.max`); + } + } else if ("min" in raw || "max" in raw) { + errors.push(`${at}.min and ${at}.max are only valid for number fields`); + } + }); + + // Second pass: every key is known by now, so forward references are fine. + fields.forEach((raw, index) => { + if (!isPlainObject(raw) || !("requires" in raw)) return; + const at = `${where}[${index}]`; + if (typeof raw.requires !== "string") { + errors.push(`${at}.requires must be a string`); + return; + } + if (!booleanKeys.has(raw.requires)) { + errors.push( + `${at}.requires "${raw.requires}" must name a boolean field in the same scope`, + ); + } + }); +} + +function validateSettingsOptions( + options: unknown, + where: string, + errors: string[], +): void { + if (!Array.isArray(options) || options.length === 0) { + errors.push(`${where}.options must be a non-empty array`); + return; + } + const seen = new Set(); + options.forEach((raw, index) => { + const at = `${where}.options[${index}]`; + if (!isPlainObject(raw)) { + errors.push(`${at} must be an object`); + return; + } + rejectUnknown(raw, ["value", "labelKey"], at, errors); + if (requireString(raw.value, `${at}.value`, errors)) { + if (seen.has(raw.value)) { + errors.push(`${at}.value duplicates "${raw.value}"`); + } + seen.add(raw.value); + } + requireString(raw.labelKey, `${at}.labelKey`, errors); + }); +} + function validateTabs(tabs: unknown, errors: string[]): void { if (tabs === undefined) return; if (!Array.isArray(tabs)) { @@ -850,6 +1131,27 @@ export function parseManifest(raw: unknown): ParsedManifest { ); } } + // A settings field may gate on one of this plugin's own permissions, or on + // a core/other-plugin id used as given. Only the first form is checkable + // here, and an undeclared one would hide the field from every admin. + const settings = manifest.contributes?.settings; + const settingsScopes: [string, PluginSettingsField[]][] = [ + ["admin", settings?.admin ?? []], + ["user", settings?.user ?? []], + ["host", settings?.host?.fields ?? []], + ]; + for (const [scope, fields] of settingsScopes) { + for (const field of fields) { + if (!field.permission) continue; + if (field.permission.includes(".")) continue; + if (!declared.has(qualifyPermission(manifest.id, field.permission))) { + errors.push( + `contributes.settings.${scope} field "${field.key}" requires permission "${field.permission}", which is not declared in contributes.permissions`, + ); + } + } + } + for (const secret of manifest.requiresSecret ?? []) { if (secret.plugin === manifest.id) { errors.push( diff --git a/packages/plugin-sdk/src/settings.ts b/packages/plugin-sdk/src/settings.ts new file mode 100644 index 000000000..2e4880640 --- /dev/null +++ b/packages/plugin-sdk/src/settings.ts @@ -0,0 +1,203 @@ +/** + * Settings field validation, shared by core and the frontend. + * + * Both the HTTP routes and the form renderer call these, so "what counts as a + * valid value" is decided once. A field the manifest never declared has no + * entry here and is rejected rather than stored, which is what stops a PUT + * from writing arbitrary keys into a plugin's namespace. + */ + +import type { PluginSettingsField } from "./manifest.js"; + +/** The permission an admin-scope field requires when it names none itself. */ +export const ADMIN_SETTINGS_PERMISSION = "admin.plugins.manage"; + +/** What a secret looks like on the wire. The value never leaves the server. */ +export interface RedactedSecret { + set: boolean; +} + +export function isRedactedSecret(value: unknown): value is RedactedSecret { + return ( + typeof value === "object" && + value !== null && + !Array.isArray(value) && + Object.keys(value).length === 1 && + typeof (value as RedactedSecret).set === "boolean" + ); +} + +/** + * Turns whatever arrived on the wire into the type the field declares. + * + * Forms send everything as strings, so a number field that refused "30" would + * reject every value the UI can produce. Anything genuinely unconvertible is + * left alone for validateSettingValue to reject with a readable message. + */ +export function coerceSettingValue( + field: PluginSettingsField, + value: unknown, +): unknown { + if (value === null || value === undefined) return value; + + switch (field.type) { + case "boolean": + if (typeof value === "boolean") return value; + if (value === "true") return true; + if (value === "false") return false; + return value; + + case "number": { + if (typeof value === "number") return value; + if (typeof value === "string" && value.trim() !== "") { + const parsed = Number(value); + return Number.isNaN(parsed) ? value : parsed; + } + return value; + } + + case "multiselect": + return Array.isArray(value) ? value : value === "" ? [] : value; + + case "string": + case "secret": + case "textarea": + case "select": + return typeof value === "number" || typeof value === "boolean" + ? String(value) + : value; + + default: + return value; + } +} + +/** + * Returns an error message, or null when the value is acceptable. + * + * Messages are plain English rather than i18n keys: they name the offending + * field and the rule it broke, and the UI shows them beside the input. + */ +export function validateSettingValue( + field: PluginSettingsField, + value: unknown, +): string | null { + // Clearing a field is always allowed; the default takes over on read. + if (value === null || value === undefined) return null; + + switch (field.type) { + case "boolean": + return typeof value === "boolean" + ? null + : `"${field.key}" must be true or false`; + + case "number": { + if (typeof value !== "number" || !Number.isFinite(value)) { + return `"${field.key}" must be a number`; + } + if (field.min !== undefined && value < field.min) { + return `"${field.key}" must be at least ${field.min}`; + } + if (field.max !== undefined && value > field.max) { + return `"${field.key}" must be at most ${field.max}`; + } + return null; + } + + case "select": { + if (typeof value !== "string") { + return `"${field.key}" must be a string`; + } + const allowed = (field.options ?? []).map((option) => option.value); + return allowed.includes(value) + ? null + : `"${field.key}" must be one of: ${allowed.join(", ")}`; + } + + case "multiselect": { + if (!Array.isArray(value)) { + return `"${field.key}" must be an array`; + } + const allowed = (field.options ?? []).map((option) => option.value); + for (const entry of value) { + if (typeof entry !== "string" || !allowed.includes(entry)) { + return `"${field.key}" may only contain: ${allowed.join(", ")}`; + } + } + return null; + } + + case "json": { + if (typeof value === "string") { + try { + JSON.parse(value); + return null; + } catch { + return `"${field.key}" must be valid JSON`; + } + } + // Already-parsed JSON is fine as long as it survives a round trip. + try { + JSON.stringify(value); + return null; + } catch { + return `"${field.key}" must be valid JSON`; + } + } + + case "secret": + // A redacted echo is a no-op rather than a value, handled by the caller. + if (isRedactedSecret(value)) return null; + return typeof value === "string" + ? null + : `"${field.key}" must be a string`; + + case "string": + case "textarea": + return typeof value === "string" + ? null + : `"${field.key}" must be a string`; + + case "custom": + // The component owns its own shape; core only stores what it sends. + return null; + + default: + return `"${field.key}" has an unknown type`; + } +} + +/** The declared defaults for a scope, used when no row has been written. */ +export function defaultsFor( + fields: readonly PluginSettingsField[] = [], +): Record { + const defaults: Record = {}; + for (const field of fields) { + if (field.default !== undefined) defaults[field.key] = field.default; + } + return defaults; +} + +/** Looks a field up by key, for a caller holding only the declared list. */ +export function findSettingsField( + fields: readonly PluginSettingsField[] = [], + key: string, +): PluginSettingsField | undefined { + return fields.find((field) => field.key === key); +} + +/** + * Whether a field should be shown or accepted, given the rest of the scope. + * + * A field declaring `requires` is inert while its named boolean is off, so the + * UI hides it and a write to it is pointless rather than wrong. + */ +export function isFieldActive( + field: PluginSettingsField, + values: Record, +): boolean { + if (!field.requires) return true; + return values[field.requires] === true; +} + +export type { PluginSettingsField } from "./manifest.js"; diff --git a/packages/plugin-sdk/src/testing.ts b/packages/plugin-sdk/src/testing.ts index 9ac6e666c..93065ae3d 100644 --- a/packages/plugin-sdk/src/testing.ts +++ b/packages/plugin-sdk/src/testing.ts @@ -25,6 +25,10 @@ export interface FakeContextOptions { manifest?: Partial; /** Acting user returned by currentActor and used by asUser. */ actor?: string; + /** Seed for admin-scope ctx.settings. */ + settings?: Record; + /** Seed for the core settings ctx.settings.readCore can reach. */ + coreSettings?: Record; } export interface FakePluginContext { @@ -43,6 +47,19 @@ export interface FakePluginContext { wsRoutes: Array<{ path: string; raw: boolean }>; /** Router options passed to ctx.http.router, in order. */ httpRouters: Array; + /** Backing store behind ctx.settings, keyed "::". */ + settings: Map; + /** Core settings readable through ctx.settings.readCore. */ + coreSettings: Map; +} + +/** How a settings row is keyed in the doubles. Mirrors the real unique index. */ +function settingsKey( + scope: string, + scopeId: string | number | undefined, + key: string, +): string { + return `${scope}:${scopeId ?? ""}:${key}`; } function noopLogger(): PluginLogger { @@ -71,8 +88,27 @@ export function createFakeContext( const wsRoutes: Array<{ path: string; raw: boolean }> = []; const httpRouters: Array = []; const listeners = new Map void>>(); + const settings = new Map(); + const coreSettings = new Map( + Object.entries(options.coreSettings ?? {}), + ); + const settingsListeners = new Map void>>(); let actor = options.actor; + for (const [key, value] of Object.entries(options.settings ?? {})) { + settings.set(settingsKey("admin", undefined, key), value); + } + + const writeSetting = ( + scope: string, + scopeId: string | number | undefined, + key: string, + value: unknown, + ) => { + settings.set(settingsKey(scope, scopeId, key), value); + for (const listener of settingsListeners.get(key) ?? []) listener(value); + }; + const manifest = { id: pluginId, name: pluginId, @@ -186,6 +222,49 @@ export function createFakeContext( require: () => ((_req, _res, next) => next()) as PluginMiddleware, }, + settings: { + get: async (key) => + settings.get(settingsKey("admin", undefined, key)) as never, + set: async (key, value) => writeSetting("admin", undefined, key, value), + + getUser: async (userId, key) => + settings.get(settingsKey("user", userId, key)) as never, + setUser: async (userId, key, value) => + writeSetting("user", userId, key, value), + + getHost: async (hostId, key) => + settings.get(settingsKey("host", String(hostId), key)) as never, + setHost: async (hostId, key, value) => + writeSetting("host", String(hostId), key, value), + + getAll: async (scope, scopeId) => { + const prefix = `${scope}:${scopeId ?? ""}:`; + const all: Record = {}; + for (const [storedKey, value] of settings) { + if (storedKey.startsWith(prefix)) { + all[storedKey.slice(prefix.length)] = value; + } + } + return all; + }, + + onChange: (key, listener) => { + let set = settingsListeners.get(key); + if (!set) { + set = new Set(); + settingsListeners.set(key, set); + } + set.add(listener); + const unsubscribe = () => { + set!.delete(listener); + }; + disposals.push(unsubscribe); + return unsubscribe; + }, + + readCore: async (key) => coreSettings.get(key) ?? null, + }, + disposables, asUser: async (userId, fn) => { @@ -210,6 +289,8 @@ export function createFakeContext( syncEntities, wsRoutes, httpRouters, + settings, + coreSettings, }; } @@ -220,15 +301,15 @@ export interface MockContextOptions { capabilities?: string[]; /** Seed for ctx.kv. */ kv?: Record; - /** Plugin settings, readable through settings.get. A5 wires the real one. */ + /** Seed for this plugin's own admin-scope settings. */ settings?: Record; + /** Seed for the core settings readCore can reach. */ + coreSettings?: Record; /** Acting user returned by currentActor and used by asUser. */ actor?: string; } export interface MockPluginContext extends FakePluginContext { - /** Settings backing store, so a test can assert what a plugin wrote. */ - settings: Map; /** Capability ids checked during the test, in order, denied ones included. */ checked: string[]; } @@ -237,22 +318,23 @@ export interface MockPluginContext extends FakePluginContext { * A context double that gates on capabilities the way src/backend/plugins/ctx.ts * does, so a test can prove a plugin fails closed without a running server. * - * Two gates exist on today's ctx surface: kv:own on every ctx.kv call, and - * events:core on emitting a topic outside the plugin's own namespace. As the - * SDK grows a member, add its gate here in the same shape. + * The gates on today's ctx surface: kv:own on every ctx.kv call, db:own on + * ctx.db, network:serve on ctx.http and ctx.ws, events:core on emitting a + * topic outside the plugin's own namespace, and settings:read-core on + * ctx.settings.readCore. Reading a plugin's own settings is deliberately + * ungated. As the SDK grows a member, add its gate here in the same shape. */ export function createMockCtx( options: MockContextOptions = {}, ): MockPluginContext { const granted = new Set(options.capabilities ?? []); const checked: string[] = []; - const settings = new Map( - Object.entries(options.settings ?? {}), - ); const base = createFakeContext({ pluginId: options.pluginId, actor: options.actor, + settings: options.settings, + coreSettings: options.coreSettings, manifest: { capabilities: options.capabilities ?? [], ...options.manifest, @@ -345,9 +427,19 @@ export function createMockCtx( ctx.ws.upgrade(path, handler, wsOptions); }, }, + + settings: { + // Reading and writing a plugin's OWN settings is ungated, exactly as in + // the real runtime. Only readCore reaches outside the plugin. + ...ctx.settings, + readCore: async (key) => { + require("settings:read-core"); + return ctx.settings.readCore(key); + }, + }, }; - return { ...base, ctx: gatedCtx, settings, checked }; + return { ...base, ctx: gatedCtx, checked }; } /** diff --git a/plugins/tailscale/manifest.json b/plugins/tailscale/manifest.json index 771f31667..2e2dd4be1 100644 --- a/plugins/tailscale/manifest.json +++ b/plugins/tailscale/manifest.json @@ -27,7 +27,34 @@ "titleKey": "permissions.devices.view.title", "descriptionKey": "permissions.devices.view.description" } - ] + ], + "settings": { + "admin": [ + { + "key": "apiKey", + "type": "secret", + "labelKey": "settings.apiKey.label", + "descriptionKey": "settings.apiKey.description", + "placeholderKey": "settings.apiKey.placeholder", + "group": "settings.group.api" + }, + { + "key": "apiBaseUrl", + "type": "string", + "labelKey": "settings.apiBaseUrl.label", + "descriptionKey": "settings.apiBaseUrl.description", + "placeholderKey": "settings.apiBaseUrl.placeholder", + "default": "", + "group": "settings.group.api" + }, + { + "key": "devices", + "type": "custom", + "component": "devices", + "group": "settings.group.devices" + } + ] + } }, "locales": "locales", "platforms": ["linux", "win32", "darwin"] diff --git a/plugins/tailscale/src/backend/index.ts b/plugins/tailscale/src/backend/index.ts index 64cda93e5..707d178f2 100644 --- a/plugins/tailscale/src/backend/index.ts +++ b/plugins/tailscale/src/backend/index.ts @@ -2,7 +2,7 @@ import type { PluginContext } from "@termix/plugin-sdk/backend"; import { startTailscaleService, stopTailscaleService } from "./routes.js"; export async function activate(ctx: PluginContext) { - startTailscaleService(ctx.http.router()); + startTailscaleService(ctx.http.router(), ctx); ctx.log.info("Tailscale routes mounted at /plugin-api/tailscale"); } diff --git a/plugins/tailscale/src/backend/routes.ts b/plugins/tailscale/src/backend/routes.ts index 4803ca1b5..ea5664897 100644 --- a/plugins/tailscale/src/backend/routes.ts +++ b/plugins/tailscale/src/backend/routes.ts @@ -8,7 +8,7 @@ import { AuthManager } from "../../../../src/backend/utils/auth-manager.js"; import { PermissionManager } from "../../../../src/backend/utils/permission-manager.js"; import { apiLogger } from "../../../../src/backend/utils/logger.js"; import { fetchWithProxy } from "../../../../src/backend/utils/proxy-agent.js"; -import { createCurrentSettingsRepository } from "../../../../src/backend/database/repositories/factory.js"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; interface TailscaleDevice { id: string; @@ -33,6 +33,12 @@ const DEFAULT_TAILSCALE_API_BASE = "https://api.tailscale.com/api/v2"; export const router = Router(); +/** + * Set by activate(). The API key and base URL are this plugin's own settings + * now, read through ctx rather than out of the core settings table. + */ +let context: PluginContext | null = null; + const authManager = AuthManager.getInstance(); const authenticateJWT = authManager.createAuthMiddleware(); @@ -85,13 +91,17 @@ router.get( requireDeviceAccess, async (_req, res) => { try { - const settingsRepo = createCurrentSettingsRepository(); - const apiKey = (await settingsRepo.get("tailscale_api_key")) ?? ""; + if (!context) { + return res + .status(503) + .json({ error: "Plugin is not running", devices: [] }); + } + const apiKey = (await context.settings.get("apiKey")) ?? ""; if (!apiKey) { return res.json({ devices: [], hasApiKey: false }); } const apiBase = normalizeApiBase( - await settingsRepo.get("tailscale_api_base_url"), + (await context.settings.get("apiBaseUrl")) ?? null, ); const url = `${apiBase}/tailnet/-/devices?fields=all`; @@ -148,12 +158,18 @@ router.get( /** Called from activate(). Mounts this router at /tailscale via the shared * dispatcher. */ -export function startTailscaleService(mountOn: Router): void { +export function startTailscaleService( + mountOn: Router, + ctx: PluginContext, +): void { + context = ctx; mountOn.use(router); } /** Called from deactivate(). /tailscale/* falls back to 404 until * reactivated. */ export function stopTailscaleService(): void { - // Unmounted by the runtime when the plugin deactivates. + // Dropped so a request arriving mid-teardown cannot read settings through a + // context the runtime has already torn down. + context = null; } diff --git a/plugins/tailscale/src/frontend/AdminTailscaleSection.tsx b/plugins/tailscale/src/frontend/AdminTailscaleSection.tsx deleted file mode 100644 index 5aa9f40e0..000000000 --- a/plugins/tailscale/src/frontend/AdminTailscaleSection.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import type { Dispatch, SetStateAction } from "react"; -import { useTranslation } from "react-i18next"; -import { Button } from "@/components/button"; -import { Input } from "@/components/input"; -import { Network } from "lucide-react"; -import { AccordionSection } from "@/sidebar/AdminSettingsShared"; - -// Was inline in AdminGeneralSettingsSection (src/ui/sidebar/AdminSettingsSections.tsx), -// moved here into its own accordion section as part of the Tailscale plugin. -// The backing routes (GET/PATCH /users/tailscale-settings) stay in core - -// they are general user-settings infrastructure this plugin calls, not -// something it owns. - -type AdminTailscaleSectionProps = { - open: boolean; - onToggle: () => void; - tailscaleApiKey: string; - setTailscaleApiKey: Dispatch>; - tailscaleApiBaseUrl: string; - setTailscaleApiBaseUrl: Dispatch>; - handleSaveTailscaleApiKey: () => void; -}; - -export function AdminTailscaleSection({ - open, - onToggle, - tailscaleApiKey, - setTailscaleApiKey, - tailscaleApiBaseUrl, - setTailscaleApiBaseUrl, - handleSaveTailscaleApiKey, -}: AdminTailscaleSectionProps) { - const { t } = useTranslation(); - - return ( - } - open={open} - onToggle={onToggle} - > -
-
- - {t("admin.tailscaleApiKey")} - - - {t("admin.tailscaleApiKeyDescription")}{" "} - - {t("admin.tailscaleApiKeyDocsLink")} - - -
-
- setTailscaleApiKey(e.target.value)} - placeholder="tskey-api-... / hskey-api-..." - className="text-sm" - /> - -
-
- - - {t("admin.tailscaleApiBaseUrlDescription")} - - setTailscaleApiBaseUrl(e.target.value)} - placeholder="https://api.tailscale.com/api/v2" - className="text-sm" - /> -
-
-
- ); -} diff --git a/plugins/tailscale/src/frontend/tailscale-api.ts b/plugins/tailscale/src/frontend/tailscale-api.ts index 7d3befa61..e03673d40 100644 --- a/plugins/tailscale/src/frontend/tailscale-api.ts +++ b/plugins/tailscale/src/frontend/tailscale-api.ts @@ -1,39 +1,11 @@ -// Was part of src/ui/api/settings-api.ts, moved here with the rest of the -// Tailscale plugin. The backing routes stay in place: /users/tailscale-settings -// is general user-settings infrastructure in core, and /tailscale/devices is -// this plugin's own route (plugins/tailscale/backend/routes.ts). +// The API key and base URL are this plugin's own settings now, read and +// written through the generic /plugins/tailscale/settings/admin routes that +// core renders from the manifest. Only the device list is left here, because +// it is this plugin's own route. import axios from "axios"; import { authApi, handleApiError } from "@/main-axios"; -export async function getTailscaleSettings(): Promise<{ - apiKey: string; - hasApiKey: boolean; - apiBaseUrl: string; -}> { - try { - const response = await authApi.get("/users/tailscale-settings"); - return response.data; - } catch (error) { - handleApiError(error, "fetch Tailscale settings"); - } -} - -export async function updateTailscaleSettings( - apiKey: string, - apiBaseUrl?: string, -): Promise<{ hasApiKey: boolean }> { - try { - const response = await authApi.patch("/users/tailscale-settings", { - apiKey, - apiBaseUrl, - }); - return response.data; - } catch (error) { - handleApiError(error, "update Tailscale settings"); - } -} - export async function getTailscaleDevices(): Promise<{ devices: Array<{ id: string; diff --git a/plugins/tailscale/tests/backend/settings.test.ts b/plugins/tailscale/tests/backend/settings.test.ts new file mode 100644 index 000000000..4645e400a --- /dev/null +++ b/plugins/tailscale/tests/backend/settings.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import { parseManifest } from "@termix/plugin-sdk/manifest"; +import { createMockCtx } from "@termix/plugin-sdk/testing"; + +/** + * The API key and base URL are this plugin's own settings now. + * + * They used to live in the core `settings` table behind a core route that + * existed only for this plugin, with the key stored in plaintext. These tests + * pin the shape core renders the page from, and that reading a plugin's own + * settings needs no capability. + */ +const manifest = parseManifest( + JSON.parse( + fs.readFileSync( + path.resolve(import.meta.dirname, "../../manifest.json"), + "utf8", + ), + ), +); + +describe("tailscale settings contribution", () => { + it("parses cleanly", () => { + expect(manifest.errors).toEqual([]); + }); + + it("declares the API key as a secret, so it is encrypted and redacted", () => { + const apiKey = manifest.manifest?.contributes?.settings?.admin?.find( + (field) => field.key === "apiKey", + ); + + expect(apiKey?.type).toBe("secret"); + }); + + it("declares the base URL as plain text, because it is not a secret", () => { + const baseUrl = manifest.manifest?.contributes?.settings?.admin?.find( + (field) => field.key === "apiBaseUrl", + ); + + expect(baseUrl?.type).toBe("string"); + expect(baseUrl?.default).toBe(""); + }); + + it("puts both fields on the admin scope, not the user one", () => { + const settings = manifest.manifest?.contributes?.settings; + + expect(settings?.admin?.map((field) => field.key)).toContain("apiKey"); + expect(settings?.user).toBeUndefined(); + }); + + it("keeps settings:read-core, which it still holds for core keys", () => { + expect(manifest.manifest?.capabilities).toContain("settings:read-core"); + }); +}); + +describe("reading its own settings", () => { + it("needs no capability", async () => { + // Nothing granted at all: a plugin reading its own configuration must not + // have to declare anything for it. + const { ctx } = createMockCtx({ + pluginId: "tailscale", + capabilities: [], + settings: { apiKey: "tskey-api-test", apiBaseUrl: "" }, + }); + + await expect(ctx.settings.get("apiKey")).resolves.toBe("tskey-api-test"); + }); + + it("can write and read a value back", async () => { + const { ctx } = createMockCtx({ pluginId: "tailscale", capabilities: [] }); + + await ctx.settings.set("apiBaseUrl", "https://headscale.example"); + + await expect(ctx.settings.get("apiBaseUrl")).resolves.toBe( + "https://headscale.example", + ); + }); + + it("still needs settings:read-core to reach a core key", async () => { + const { ctx } = createMockCtx({ pluginId: "tailscale", capabilities: [] }); + + await expect(ctx.settings.readCore("app_name")).rejects.toThrow( + /settings:read-core/, + ); + }); +}); diff --git a/plugins/tailscale/tests/frontend/AdminTailscaleSection.test.tsx b/plugins/tailscale/tests/frontend/AdminTailscaleSection.test.tsx deleted file mode 100644 index 7ecbc0e08..000000000 --- a/plugins/tailscale/tests/frontend/AdminTailscaleSection.test.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { AdminTailscaleSection } from "../../src/frontend/AdminTailscaleSection"; - -vi.mock("react-i18next", () => ({ - useTranslation: () => ({ t: (key: string) => key }), -})); - -describe("AdminTailscaleSection", () => { - it("renders the API key and base URL fields and saves on click", async () => { - const user = userEvent.setup(); - const handleSave = vi.fn(); - render( - , - ); - - expect( - ( - screen.getByPlaceholderText( - "tskey-api-... / hskey-api-...", - ) as HTMLInputElement - ).value, - ).toBe("tskey-api-abc"); - expect( - ( - screen.getByPlaceholderText( - "https://api.tailscale.com/api/v2", - ) as HTMLInputElement - ).value, - ).toBe("https://api.tailscale.com/api/v2"); - - await user.click(screen.getByText("common.save")); - expect(handleSave).toHaveBeenCalledOnce(); - }); - - it("does not render its content when closed", () => { - render( - , - ); - - expect( - screen.queryByPlaceholderText("tskey-api-... / hskey-api-..."), - ).toBeNull(); - }); -}); diff --git a/scripts/plugin-boundary-allowlist.json b/scripts/plugin-boundary-allowlist.json index 20a4f60c2..a6b8076aa 100644 --- a/scripts/plugin-boundary-allowlist.json +++ b/scripts/plugin-boundary-allowlist.json @@ -9,8 +9,8 @@ "src/ui/features/session-sharing/SharedSessionView.tsx", "src/ui/features/terminal/Terminal.tsx", "src/ui/main-axios.ts", + "src/ui/settings/components/TailscaleDevicesStatus.tsx", "src/ui/shell/tabUtils.tsx", - "src/ui/sidebar/AdminSettingsPanel.tsx", "src/ui/sidebar/HostEditor.tsx", "src/ui/sidebar/HostEditorWebUiSection.tsx", "src/ui/sidebar/HostsPanel.tsx", diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index 27283e58e..0270965ff 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -751,6 +751,21 @@ async function initializeCompleteDatabase(): Promise { FOREIGN KEY (plugin_id) REFERENCES plugins (id) ON DELETE CASCADE ); + CREATE TABLE IF NOT EXISTS plugin_settings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plugin_id TEXT NOT NULL, + scope TEXT NOT NULL, + scope_id TEXT, + key TEXT NOT NULL, + value TEXT, + encrypted INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (plugin_id, scope, scope_id, key), + FOREIGN KEY (plugin_id) REFERENCES plugins (id) ON DELETE CASCADE + ); + + CREATE INDEX IF NOT EXISTS idx_plugin_settings_plugin_scope ON plugin_settings (plugin_id, scope); + CREATE TABLE IF NOT EXISTS rbac_known_permissions ( id INTEGER PRIMARY KEY AUTOINCREMENT, permission TEXT NOT NULL, diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index d35677447..902712377 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -2252,6 +2252,49 @@ export const pluginStorage = mysqlTable( ], ); +/** + * Values for the settings fields a plugin declares in contributes.settings. + * + * scope_id is polymorphic: null for admin scope, a user id for user scope, a + * host id rendered as text for host scope. That is why it carries no foreign + * key - one column cannot point at two tables - so the user and host delete + * paths remove these rows explicitly. Cascading with the plugin is a real FK, + * because uninstalling should leave nothing behind. + * + * Secret fields are encrypted with the system key before they land here, and + * `encrypted` records which rows that applies to so a read knows to decrypt. + */ +export const pluginSettings = mysqlTable( + "plugin_settings", + { + id: int("id").autoincrement().primaryKey(), + pluginId: varchar("plugin_id", { length: 255 }) + .notNull() + .references(() => plugins.id, { onDelete: "cascade" }), + /** admin | user | host */ + scope: varchar("scope", { length: 255 }).notNull(), + scopeId: varchar("scope_id", { length: 255 }), + key: varchar("key", { length: 255 }).notNull(), + /** JSON-encoded, so a field keeps its declared type across a round trip. */ + value: text("value"), + encrypted: boolean("encrypted") + .notNull() + .default(false), + updatedAt: varchar("updated_at", { length: 255 }) + .notNull() + .default(sql`(CURRENT_TIMESTAMP)`), + }, + (table) => [ + uniqueIndex("idx_plugin_settings_scope_key").on( + table.pluginId, + table.scope, + table.scopeId, + table.key, + ), + index("idx_plugin_settings_plugin_scope").on(table.pluginId, table.scope), + ], +); + /** * Which of a plugin's migrations have been applied. * diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index c67e84f4b..2efd0ac9d 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -2253,6 +2253,49 @@ export const pluginStorage = pgTable( ], ); +/** + * Values for the settings fields a plugin declares in contributes.settings. + * + * scope_id is polymorphic: null for admin scope, a user id for user scope, a + * host id rendered as text for host scope. That is why it carries no foreign + * key - one column cannot point at two tables - so the user and host delete + * paths remove these rows explicitly. Cascading with the plugin is a real FK, + * because uninstalling should leave nothing behind. + * + * Secret fields are encrypted with the system key before they land here, and + * `encrypted` records which rows that applies to so a read knows to decrypt. + */ +export const pluginSettings = pgTable( + "plugin_settings", + { + id: serial("id").primaryKey(), + pluginId: varchar("plugin_id", { length: 255 }) + .notNull() + .references(() => plugins.id, { onDelete: "cascade" }), + /** admin | user | host */ + scope: varchar("scope", { length: 255 }).notNull(), + scopeId: varchar("scope_id", { length: 255 }), + key: varchar("key", { length: 255 }).notNull(), + /** JSON-encoded, so a field keeps its declared type across a round trip. */ + value: text("value"), + encrypted: boolean("encrypted") + .notNull() + .default(false), + updatedAt: varchar("updated_at", { length: 255 }) + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + }, + (table) => [ + uniqueIndex("idx_plugin_settings_scope_key").on( + table.pluginId, + table.scope, + table.scopeId, + table.key, + ), + index("idx_plugin_settings_plugin_scope").on(table.pluginId, table.scope), + ], +); + /** * Which of a plugin's migrations have been applied. * diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index 2145d5ad1..85af39619 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -2251,6 +2251,49 @@ export const pluginStorage = sqliteTable( ], ); +/** + * Values for the settings fields a plugin declares in contributes.settings. + * + * scope_id is polymorphic: null for admin scope, a user id for user scope, a + * host id rendered as text for host scope. That is why it carries no foreign + * key - one column cannot point at two tables - so the user and host delete + * paths remove these rows explicitly. Cascading with the plugin is a real FK, + * because uninstalling should leave nothing behind. + * + * Secret fields are encrypted with the system key before they land here, and + * `encrypted` records which rows that applies to so a read knows to decrypt. + */ +export const pluginSettings = sqliteTable( + "plugin_settings", + { + id: integer("id").primaryKey({ autoIncrement: true }), + pluginId: text("plugin_id") + .notNull() + .references(() => plugins.id, { onDelete: "cascade" }), + /** admin | user | host */ + scope: text("scope").notNull(), + scopeId: text("scope_id"), + key: text("key").notNull(), + /** JSON-encoded, so a field keeps its declared type across a round trip. */ + value: text("value"), + encrypted: integer("encrypted", { mode: "boolean" }) + .notNull() + .default(false), + updatedAt: text("updated_at") + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + }, + (table) => [ + uniqueIndex("idx_plugin_settings_scope_key").on( + table.pluginId, + table.scope, + table.scopeId, + table.key, + ), + index("idx_plugin_settings_plugin_scope").on(table.pluginId, table.scope), + ], +); + /** * Which of a plugin's migrations have been applied. * diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts index 3b0c5a1f7..2e0748cf6 100644 --- a/src/backend/database/repositories/factory.ts +++ b/src/backend/database/repositories/factory.ts @@ -34,6 +34,7 @@ import { OpenTabRepository } from "./open-tab-repository.js"; import { OpksshTokenRepository } from "./opkssh-token-repository.js"; import { PluginRepository } from "./plugin-repository.js"; import { PluginStorageRepository } from "./plugin-storage-repository.js"; +import { PluginSettingsRepository } from "./plugin-settings-repository.js"; import { PluginMigrationRepository } from "./plugin-migration-repository.js"; import { PluginPermissionGrantRepository } from "./plugin-permission-grant-repository.js"; import { PluginRegistryRepository } from "./plugin-registry-repository.js"; @@ -395,6 +396,13 @@ export function createCurrentPluginStorageRepository(): PluginStorageRepository ); } +export function createCurrentPluginSettingsRepository(): PluginSettingsRepository { + return new PluginSettingsRepository( + createCurrentRepositoryContext(), + createCurrentRepositoryWriteHook("plugin_settings_repository_write"), + ); +} + export function createCurrentPluginMigrationRepository(): PluginMigrationRepository { return new PluginMigrationRepository( createCurrentRepositoryContext(), diff --git a/src/backend/database/repositories/host-repository.ts b/src/backend/database/repositories/host-repository.ts index 3a4ab9735..7dea2a390 100644 --- a/src/backend/database/repositories/host-repository.ts +++ b/src/backend/database/repositories/host-repository.ts @@ -1,6 +1,6 @@ import { and, eq, inArray, sql } from "drizzle-orm"; import { randomUUID } from "crypto"; -import { hostAccess, hosts } from "../db/schema.js"; +import { hostAccess, hosts, pluginSettings } from "../db/schema.js"; import type { DatabaseContext } from "./database-context.js"; import { DataCrypto } from "../../utils/data-crypto.js"; import { rowsAffected } from "./mutation-result.js"; @@ -338,6 +338,25 @@ export class HostRepository { } async deleteByUserId(userId: string): Promise { + // Bulk path, so it never passes through deleteAccessForHost: clear the + // host-scope plugin settings for these hosts before the rows go. + const owned = await this.context.drizzle + .select({ id: hosts.id }) + .from(hosts) + .where(eq(hosts.userId, userId)); + + if (owned.length > 0) { + await this.context.drizzle.delete(pluginSettings).where( + and( + eq(pluginSettings.scope, "host"), + inArray( + pluginSettings.scopeId, + owned.map((row) => String(row.id)), + ), + ), + ); + } + const result = await this.context.drizzle .delete(hosts) .where(eq(hosts.userId, userId)); @@ -350,6 +369,18 @@ export class HostRepository { } async deleteAccessForHost(hostId: number): Promise { + // plugin_settings.scope_id is polymorphic, so it has no foreign key to + // ssh_data and the engine will not cascade it. Cleared alongside the + // access rows, which every host delete path already goes through. + await this.context.drizzle + .delete(pluginSettings) + .where( + and( + eq(pluginSettings.scope, "host"), + eq(pluginSettings.scopeId, String(hostId)), + ), + ); + const result = await this.context.drizzle .delete(hostAccess) .where(eq(hostAccess.hostId, hostId)); diff --git a/src/backend/database/repositories/plugin-settings-repository.ts b/src/backend/database/repositories/plugin-settings-repository.ts new file mode 100644 index 000000000..45d534611 --- /dev/null +++ b/src/backend/database/repositories/plugin-settings-repository.ts @@ -0,0 +1,191 @@ +import { and, eq, inArray, isNull } from "drizzle-orm"; +import { pluginSettings } from "../db/schema.js"; +import type { DatabaseContext } from "./database-context.js"; +import { rowsAffected } from "./mutation-result.js"; +import { insertReturning, updateReturning } from "./returning.js"; + +export type PluginSettingsRecord = typeof pluginSettings.$inferSelect; + +export type PluginSettingsScope = "admin" | "user" | "host"; + +/** + * Values behind ctx.settings and the /plugins/:id/settings routes. + * + * scope_id is null for admin scope, so every lookup has to use IS NULL rather + * than = NULL: SQL equality against null never matches, which would make an + * admin setting look unset no matter how often it was written. + */ +export class PluginSettingsRepository { + constructor( + private readonly context: DatabaseContext, + private readonly onWrite?: () => void | Promise, + ) {} + + private scopeMatch( + pluginId: string, + scope: PluginSettingsScope, + scopeId: string | null, + ) { + return and( + eq(pluginSettings.pluginId, pluginId), + eq(pluginSettings.scope, scope), + scopeId === null + ? isNull(pluginSettings.scopeId) + : eq(pluginSettings.scopeId, scopeId), + ); + } + + async get( + pluginId: string, + scope: PluginSettingsScope, + scopeId: string | null, + key: string, + ): Promise { + const rows = await this.context.drizzle + .select() + .from(pluginSettings) + .where( + and( + this.scopeMatch(pluginId, scope, scopeId), + eq(pluginSettings.key, key), + ), + ) + .limit(1); + + return rows[0] ?? null; + } + + async getAll( + pluginId: string, + scope: PluginSettingsScope, + scopeId: string | null, + ): Promise { + return this.context.drizzle + .select() + .from(pluginSettings) + .where(this.scopeMatch(pluginId, scope, scopeId)); + } + + /** + * Every row for a set of scope ids, in one query. + * + * The host list would otherwise run one query per host, which is the shape + * that made other list endpoints slow at a few hundred hosts. + */ + async getAllForScopeIds( + scope: PluginSettingsScope, + scopeIds: string[], + ): Promise { + if (scopeIds.length === 0) return []; + + return this.context.drizzle + .select() + .from(pluginSettings) + .where( + and( + eq(pluginSettings.scope, scope), + inArray(pluginSettings.scopeId, scopeIds), + ), + ); + } + + async set( + pluginId: string, + scope: PluginSettingsScope, + scopeId: string | null, + key: string, + value: string | null, + encrypted = false, + now = new Date().toISOString(), + ): Promise { + const where = and( + this.scopeMatch(pluginId, scope, scopeId), + eq(pluginSettings.key, key), + )!; + + const existing = await this.context.drizzle + .select() + .from(pluginSettings) + .where(where) + .limit(1); + + if (existing[0]) { + await updateReturning( + this.context, + pluginSettings, + { value, encrypted, updatedAt: now }, + where, + ); + } else { + await insertReturning(this.context, pluginSettings, { + pluginId, + scope, + scopeId, + key, + value, + encrypted, + updatedAt: now, + }); + } + + await this.afterWrite(); + } + + async delete( + pluginId: string, + scope: PluginSettingsScope, + scopeId: string | null, + key: string, + ): Promise { + const result = await this.context.drizzle + .delete(pluginSettings) + .where( + and( + this.scopeMatch(pluginId, scope, scopeId), + eq(pluginSettings.key, key), + ), + ); + + const affected = rowsAffected(result) > 0; + if (affected) await this.afterWrite(); + return affected; + } + + async deleteByPlugin(pluginId: string): Promise { + const result = await this.context.drizzle + .delete(pluginSettings) + .where(eq(pluginSettings.pluginId, pluginId)); + + const affected = rowsAffected(result); + if (affected > 0) await this.afterWrite(); + return affected; + } + + /** + * Removes every plugin's rows for one user or host. + * + * scope_id carries no foreign key, so the user and host delete paths call + * this instead of relying on the engine to cascade. + */ + async deleteByScope( + scope: PluginSettingsScope, + scopeId: string, + ): Promise { + const result = await this.context.drizzle + .delete(pluginSettings) + .where( + and( + eq(pluginSettings.scope, scope), + eq(pluginSettings.scopeId, scopeId), + ), + ); + + const affected = rowsAffected(result); + if (affected > 0) await this.afterWrite(); + return affected; + } + + private async afterWrite(): Promise { + await this.onWrite?.(); + } +} diff --git a/src/backend/database/repositories/user-repository.ts b/src/backend/database/repositories/user-repository.ts index 2cc71fd7d..f6fcf7bfe 100644 --- a/src/backend/database/repositories/user-repository.ts +++ b/src/backend/database/repositories/user-repository.ts @@ -1,5 +1,5 @@ -import { asc, eq, inArray, like, sql } from "drizzle-orm"; -import { users } from "../db/schema.js"; +import { and, asc, eq, inArray, like, sql } from "drizzle-orm"; +import { pluginSettings, users } from "../db/schema.js"; import type { DatabaseContext } from "./database-context.js"; import { countValue, @@ -199,6 +199,15 @@ export class UserRepository { } async delete(id: string): Promise { + // plugin_settings.scope_id is polymorphic, so it carries no foreign key to + // users and the engine will not cascade it. Removed here rather than in a + // route so every caller that deletes a user is covered. + await this.context.drizzle + .delete(pluginSettings) + .where( + and(eq(pluginSettings.scope, "user"), eq(pluginSettings.scopeId, id)), + ); + const result = await this.context.drizzle .delete(users) .where(eq(users.id, id)); diff --git a/src/backend/database/routes/host-plugin-settings.ts b/src/backend/database/routes/host-plugin-settings.ts new file mode 100644 index 000000000..8a6089016 --- /dev/null +++ b/src/backend/database/routes/host-plugin-settings.ts @@ -0,0 +1,130 @@ +/** + * The pluginSettings map attached to a host on its way out. + * + * Without this the host editor would need one request per plugin per host, + * which is the shape that makes a list of a few hundred hosts slow. One query + * covers the whole list instead. + * + * Kept out of transformHostResponse because that function is synchronous and + * shared by every host read path. Secrets are always redacted here, so nothing + * on this path has to decrypt and the async work stays in one place. + */ + +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import { createCurrentPluginSettingsRepository } from "../repositories/factory.js"; +import type { PluginSettingsRecord } from "../repositories/plugin-settings-repository.js"; +import { declaredFields, resolveFieldValue } from "../../plugins/settings.js"; +import { getPluginRuntime } from "../../plugins/index.js"; +import { sshLogger } from "../../utils/logger.js"; + +export type HostPluginSettings = Record>; + +/** Enabled plugins that declare host-scope settings, with their manifests. */ +function hostSettingsPlugins(): PluginManifest[] { + try { + const { loader } = getPluginRuntime(); + return loader + .list() + .filter((plugin) => plugin.state === "active") + .map((plugin) => plugin.manifest) + .filter( + (manifest): manifest is PluginManifest => + !!manifest && !!manifest.contributes?.settings?.host, + ); + } catch { + // The runtime is not up in every context that renders a host. + return []; + } +} + +/** + * Builds the map for a set of hosts in one query. + * + * Returns an empty map when no enabled plugin declares host settings, so the + * common case costs nothing. + */ +export async function loadHostPluginSettings( + hostIds: number[], +): Promise> { + const result = new Map(); + const manifests = hostSettingsPlugins(); + if (manifests.length === 0 || hostIds.length === 0) return result; + + let rows: PluginSettingsRecord[] = []; + try { + rows = await createCurrentPluginSettingsRepository().getAllForScopeIds( + "host", + hostIds.map(String), + ); + } catch (error) { + // A host list must not fail because a settings read did. + sshLogger.warn("Failed to load host plugin settings", { + operation: "host_plugin_settings", + error: error instanceof Error ? error.message : String(error), + }); + return result; + } + + const byHostAndPlugin = new Map(); + for (const row of rows) { + const key = `${row.scopeId}:${row.pluginId}`; + const list = byHostAndPlugin.get(key); + if (list) list.push(row); + else byHostAndPlugin.set(key, [row]); + } + + for (const hostId of hostIds) { + const perPlugin: HostPluginSettings = {}; + + for (const manifest of manifests) { + const fields = declaredFields(manifest, "host"); + if (fields.length === 0) continue; + + const stored = new Map( + (byHostAndPlugin.get(`${hostId}:${manifest.id}`) ?? []).map((row) => [ + row.key, + row, + ]), + ); + + const values: Record = {}; + for (const field of fields) { + values[field.key] = await resolveFieldValue( + field, + stored.get(field.key), + { redactSecrets: true }, + ); + } + perPlugin[manifest.id] = values; + } + + if (Object.keys(perPlugin).length > 0) result.set(hostId, perPlugin); + } + + return result; +} + +/** Attaches the map to already-transformed host objects, in place. */ +export function attachHostPluginSettings( + hosts: Record[], + settings: Map, +): void { + if (settings.size === 0) return; + for (const host of hosts) { + const hostId = Number(host.id); + const values = settings.get(hostId); + if (values) host.pluginSettings = values; + } +} + +/** The single-host convenience wrapper. */ +export async function withHostPluginSettings( + host: Record, +): Promise> { + const hostId = Number(host.id); + if (!Number.isInteger(hostId)) return host; + + const settings = await loadHostPluginSettings([hostId]); + const values = settings.get(hostId); + return values ? { ...host, pluginSettings: values } : host; +} diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index da4ef7051..5a0e29aba 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -45,6 +45,11 @@ import { stripSensitiveFields, transformHostResponse, } from "./host-normalizers.js"; +import { + attachHostPluginSettings, + loadHostPluginSettings, + withHostPluginSettings, +} from "./host-plugin-settings.js"; import { validateParentHostId } from "./host-parent-validation.js"; import { registerHostOpksshRoutes } from "./host-opkssh-routes.js"; import { registerHostStepCaRoutes } from "./host-step-ca-routes.js"; @@ -1701,6 +1706,18 @@ router.get( ) : stripSensitiveFields(host), ); + + // After sanitizing: the connect-level projection reduces a shared host + // to an allowlist, which would drop this again. One query for the list. + attachHostPluginSettings( + sanitized, + await loadHostPluginSettings( + sanitized + .map((host) => Number(host.id)) + .filter((id) => Number.isInteger(id)), + ), + ); + res.json(sanitized); } catch (err) { sshLogger.error("Failed to fetch SSH hosts from database", err, { @@ -1767,7 +1784,9 @@ router.get( const resolved = (await resolveHostCredentials(result, userId)) || result; - return res.json(stripSensitiveFields(resolved)); + return res.json( + await withHostPluginSettings(stripSensitiveFields(resolved)), + ); } // Not the owner: shared recipients get a sanitized view of the host. @@ -1818,9 +1837,11 @@ router.get( (await resolveHostCredentials(sharedResult, userId)) || sharedResult; res.json( - sanitizeHostForRecipient( - resolvedSharedResult, - accessInfo.permissionLevel, + await withHostPluginSettings( + sanitizeHostForRecipient( + resolvedSharedResult, + accessInfo.permissionLevel, + ), ), ); } catch (err) { diff --git a/src/backend/database/routes/plugins.ts b/src/backend/database/routes/plugins.ts index cf54a9673..aebdb79fa 100644 --- a/src/backend/database/routes/plugins.ts +++ b/src/backend/database/routes/plugins.ts @@ -14,9 +14,29 @@ import { } from "../repositories/factory.js"; import { getPluginRuntime } from "../../plugins/index.js"; import { invalidatePluginPermissionCache } from "../../plugins/permissions.js"; +import { + findField, + getAllSettings, + setSetting, + type PluginSettingsScope, +} from "../../plugins/settings.js"; +import { + getAuditUsername, + getRequestMeta, + logAudit, +} from "../../utils/audit-logger.js"; +import type { + PluginManifest, + PluginSettingsField, +} from "@termix/plugin-sdk/manifest"; const router = express.Router(); +/** A settings body is a flat object; an array or null is a client bug. */ +function isPlainBody(body: unknown): body is Record { + return typeof body === "object" && body !== null && !Array.isArray(body); +} + const authManager = AuthManager.getInstance(); const authenticateJWT = authManager.createAuthMiddleware(); const permissionManager = PermissionManager.getInstance(); @@ -79,15 +99,18 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => { let contributes: unknown = null; let capabilities: string[] = []; + let icon: string | undefined; try { const manifest = JSON.parse(record.manifestJson) as { contributes?: unknown; capabilities?: unknown; + icon?: unknown; }; contributes = manifest?.contributes ?? null; capabilities = Array.isArray(manifest?.capabilities) ? (manifest.capabilities as string[]) : []; + icon = typeof manifest?.icon === "string" ? manifest.icon : undefined; } catch { contributes = null; } @@ -99,6 +122,7 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => { enabled: record.state === "enabled", state: loaded?.state ?? record.state, contributes, + icon, }; if (!canManage) return summary; @@ -528,4 +552,535 @@ router.delete( }, ); +/** + * Loads a plugin's manifest for the settings routes. + * + * Read from the loader rather than the database row so the fields a request is + * validated against are the ones the running build declares. + */ +async function loadManifestForSettings( + pluginId: string, +): Promise { + const { loader } = getPluginRuntime(); + const live = loader.get(pluginId); + if (live?.manifest) return live.manifest; + + const record = await createCurrentPluginRepository().findById(pluginId); + if (!record) return null; + try { + return JSON.parse(record.manifestJson) as PluginManifest; + } catch { + return null; + } +} + +/** + * Whether the caller may write a given field. + * + * Admin fields fall back to admin.plugins.manage, so a plugin that declares no + * permission of its own still cannot have its install-wide settings changed by + * an ordinary user. A field naming its own permission uses that instead. + */ +async function canWriteField( + userId: string, + manifest: PluginManifest, + scope: PluginSettingsScope, + field: PluginSettingsField, +): Promise { + // Admin scope always needs admin.plugins.manage. A field-level permission + // narrows who may write it; it never widens the scope's own gate, so a + // plugin cannot hand an ordinary user install-wide configuration by naming + // a permission that user happens to hold. + if ( + scope === "admin" && + !(await permissionManager.hasPermission(userId, "admin.plugins.manage")) + ) { + return false; + } + + if (field.permission) { + const { resolvePermission } = await import("../../plugins/rbac.js"); + return permissionManager.hasPermission( + userId, + resolvePermission(manifest, field.permission), + ); + } + + return true; +} + +/** Applies a body of values to one scope, collecting per-field errors. */ +async function applySettings( + userId: string, + manifest: PluginManifest, + scope: PluginSettingsScope, + scopeId: string | null, + body: Record, +): Promise> { + const errors: Record = {}; + + for (const [key, value] of Object.entries(body)) { + const field = findField(manifest, scope, key); + if (!field) { + errors[key] = "Not a settings field this plugin declares"; + continue; + } + if (!(await canWriteField(userId, manifest, scope, field))) { + errors[key] = "You do not have permission to change this setting"; + continue; + } + + const error = await setSetting(manifest, scope, scopeId, key, value); + if (error) errors[key] = error; + } + + return errors; +} + +/** + * @openapi + * /plugins/{id}/settings/admin: + * get: + * summary: Read a plugin's install-wide settings + * description: > + * Returns every admin-scope field the plugin declares, with stored values + * over declared defaults. Secret fields come back as { set: boolean } and + * never carry their value, so a browser can render "configured" without + * ever holding the secret. + * tags: + * - Plugins + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The plugin's admin settings. + * 403: + * description: The caller lacks admin.plugins.manage. + * 404: + * description: No such plugin. + */ +router.get( + "/:id/settings/admin", + authenticateJWT, + requireManagePlugins, + async (req: Request, res: Response) => { + const pluginId = String(req.params.id); + try { + const manifest = await loadManifestForSettings(pluginId); + if (!manifest) { + res.status(404).json({ error: "No such plugin" }); + return; + } + const values = await getAllSettings(manifest, "admin", null, { + redactSecrets: true, + }); + res.json({ id: pluginId, values }); + } catch (error) { + databaseLogger.error( + `Failed to read admin settings for plugin ${pluginId}`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_read" }, + ); + res.status(500).json({ error: "Failed to read the plugin's settings" }); + } + }, +); + +/** + * @openapi + * /plugins/{id}/settings/admin: + * put: + * summary: Update a plugin's install-wide settings + * description: > + * Writes the given keys. Every value is validated against the field the + * manifest declares, and a key the manifest never declared is rejected + * rather than stored. Validation failures come back per field so a form + * can show every problem at once. Sending a secret field back as + * { set: true } leaves the stored value alone, so saving a form does not + * clear a key it was never shown. + * tags: + * - Plugins + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: The updated settings. + * 400: + * description: One or more fields were rejected. + * 403: + * description: The caller lacks admin.plugins.manage. + * 404: + * description: No such plugin. + */ +router.put( + "/:id/settings/admin", + authenticateJWT, + requireManagePlugins, + async (req: Request, res: Response) => { + const pluginId = String(req.params.id); + const userId = (req as AuthenticatedRequest).userId as string; + + if (!isPlainBody(req.body)) { + res.status(400).json({ error: "Body must be an object of settings" }); + return; + } + + try { + const manifest = await loadManifestForSettings(pluginId); + if (!manifest) { + res.status(404).json({ error: "No such plugin" }); + return; + } + + const errors = await applySettings( + userId, + manifest, + "admin", + null, + req.body, + ); + if (Object.keys(errors).length > 0) { + res.status(400).json({ error: "Some settings were rejected", errors }); + return; + } + + const { ipAddress, userAgent } = getRequestMeta(req); + await logAudit({ + userId, + username: await getAuditUsername(userId), + action: "update_plugin_settings", + resourceType: "setting", + resourceId: pluginId, + resourceName: manifest.name, + // Keys only: a value here could be the secret we just encrypted. + details: JSON.stringify({ + scope: "admin", + keys: Object.keys(req.body), + }), + ipAddress, + userAgent, + success: true, + }); + + const values = await getAllSettings(manifest, "admin", null, { + redactSecrets: true, + }); + res.json({ id: pluginId, values }); + } catch (error) { + databaseLogger.error( + `Failed to update admin settings for plugin ${pluginId}`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_write" }, + ); + res.status(500).json({ error: "Failed to update the plugin's settings" }); + } + }, +); + +/** + * @openapi + * /plugins/{id}/settings/user: + * get: + * summary: Read the caller's own settings for a plugin + * description: Returns every user-scope field the plugin declares for the authenticated user. The scope is always the caller, never a user id from the request. + * tags: + * - Plugins + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The caller's settings for this plugin. + * 404: + * description: No such plugin. + */ +router.get( + "/:id/settings/user", + authenticateJWT, + async (req: Request, res: Response) => { + const pluginId = String(req.params.id); + const userId = (req as AuthenticatedRequest).userId as string; + + try { + const manifest = await loadManifestForSettings(pluginId); + if (!manifest) { + res.status(404).json({ error: "No such plugin" }); + return; + } + const values = await getAllSettings(manifest, "user", userId, { + redactSecrets: true, + }); + res.json({ id: pluginId, values }); + } catch (error) { + databaseLogger.error( + `Failed to read user settings for plugin ${pluginId}`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_read" }, + ); + res.status(500).json({ error: "Failed to read the plugin's settings" }); + } + }, +); + +/** + * @openapi + * /plugins/{id}/settings/user: + * put: + * summary: Update the caller's own settings for a plugin + * description: Writes user-scope values for the authenticated user. The scope is always the caller, so one user can never write another's settings. + * tags: + * - Plugins + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: The updated settings. + * 400: + * description: One or more fields were rejected. + * 404: + * description: No such plugin. + */ +router.put( + "/:id/settings/user", + authenticateJWT, + async (req: Request, res: Response) => { + const pluginId = String(req.params.id); + const userId = (req as AuthenticatedRequest).userId as string; + + if (!isPlainBody(req.body)) { + res.status(400).json({ error: "Body must be an object of settings" }); + return; + } + + try { + const manifest = await loadManifestForSettings(pluginId); + if (!manifest) { + res.status(404).json({ error: "No such plugin" }); + return; + } + + const errors = await applySettings( + userId, + manifest, + "user", + userId, + req.body, + ); + if (Object.keys(errors).length > 0) { + res.status(400).json({ error: "Some settings were rejected", errors }); + return; + } + + const values = await getAllSettings(manifest, "user", userId, { + redactSecrets: true, + }); + res.json({ id: pluginId, values }); + } catch (error) { + databaseLogger.error( + `Failed to update user settings for plugin ${pluginId}`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_write" }, + ); + res.status(500).json({ error: "Failed to update the plugin's settings" }); + } + }, +); + +/** + * @openapi + * /plugins/{id}/settings/host/{hostId}: + * get: + * summary: Read a plugin's settings for one host + * description: Returns every host-scope field the plugin declares for that host. Requires edit access to the host, the same check the host editor itself uses. + * tags: + * - Plugins + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * - in: path + * name: hostId + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: The plugin's settings for that host. + * 400: + * description: Invalid host id. + * 403: + * description: No edit access to that host. + * 404: + * description: No such plugin. + */ +router.get( + "/:id/settings/host/:hostId", + authenticateJWT, + async (req: Request, res: Response) => { + const pluginId = String(req.params.id); + const userId = (req as AuthenticatedRequest).userId as string; + const hostId = Number(req.params.hostId); + + if (!Number.isInteger(hostId) || hostId <= 0) { + res.status(400).json({ error: "Invalid host ID" }); + return; + } + + try { + const manifest = await loadManifestForSettings(pluginId); + if (!manifest) { + res.status(404).json({ error: "No such plugin" }); + return; + } + + const access = await permissionManager.canAccessHost( + userId, + hostId, + "edit", + ); + if (!access.hasAccess) { + res.status(403).json({ error: "Access denied to host" }); + return; + } + + const values = await getAllSettings(manifest, "host", hostId, { + redactSecrets: true, + }); + res.json({ id: pluginId, hostId, values }); + } catch (error) { + databaseLogger.error( + `Failed to read host settings for plugin ${pluginId}`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_read" }, + ); + res.status(500).json({ error: "Failed to read the plugin's settings" }); + } + }, +); + +/** + * @openapi + * /plugins/{id}/settings/host/{hostId}: + * put: + * summary: Update a plugin's settings for one host + * description: Writes host-scope values. Requires edit access to the host, so a user who may only connect to a shared host cannot change how a plugin treats it. + * tags: + * - Plugins + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * - in: path + * name: hostId + * required: true + * schema: + * type: integer + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: The updated settings. + * 400: + * description: Invalid host id, or one or more fields were rejected. + * 403: + * description: No edit access to that host. + * 404: + * description: No such plugin. + */ +router.put( + "/:id/settings/host/:hostId", + authenticateJWT, + async (req: Request, res: Response) => { + const pluginId = String(req.params.id); + const userId = (req as AuthenticatedRequest).userId as string; + const hostId = Number(req.params.hostId); + + if (!Number.isInteger(hostId) || hostId <= 0) { + res.status(400).json({ error: "Invalid host ID" }); + return; + } + if (!isPlainBody(req.body)) { + res.status(400).json({ error: "Body must be an object of settings" }); + return; + } + + try { + const manifest = await loadManifestForSettings(pluginId); + if (!manifest) { + res.status(404).json({ error: "No such plugin" }); + return; + } + + const access = await permissionManager.canAccessHost( + userId, + hostId, + "edit", + ); + if (!access.hasAccess) { + res.status(403).json({ error: "Access denied to host" }); + return; + } + + const errors = await applySettings( + userId, + manifest, + "host", + String(hostId), + req.body, + ); + if (Object.keys(errors).length > 0) { + res.status(400).json({ error: "Some settings were rejected", errors }); + return; + } + + const values = await getAllSettings(manifest, "host", hostId, { + redactSecrets: true, + }); + res.json({ id: pluginId, hostId, values }); + } catch (error) { + databaseLogger.error( + `Failed to update host settings for plugin ${pluginId}`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_write" }, + ); + res.status(500).json({ error: "Failed to update the plugin's settings" }); + } + }, +); + export default router; diff --git a/src/backend/database/routes/user-settings-routes.ts b/src/backend/database/routes/user-settings-routes.ts index d11f50322..1f0483ad2 100644 --- a/src/backend/database/routes/user-settings-routes.ts +++ b/src/backend/database/routes/user-settings-routes.ts @@ -338,120 +338,6 @@ export function registerUserSettingsRoutes( } }); - /** - * @openapi - * /users/tailscale-settings: - * get: - * summary: Get Tailscale settings - * description: Returns whether a Tailscale API key is configured (value is masked). - * tags: - * - Users - * responses: - * 200: - * description: Tailscale settings. - * content: - * application/json: - * schema: - * type: object - * properties: - * apiKey: - * type: string - * description: Masked API key or empty string if not set. - * hasApiKey: - * type: boolean - * apiBaseUrl: - * type: string - * description: Custom control-plane API base URL (e.g. a Headscale instance), or empty string for the Tailscale default. - */ - router.get("/tailscale-settings", authenticateJWT, async (_req, res) => { - try { - const settingsRepo = createCurrentSettingsRepository(); - const apiKey = (await settingsRepo.get("tailscale_api_key")) ?? ""; - const apiBaseUrl = - (await settingsRepo.get("tailscale_api_base_url")) ?? ""; - res.json({ - apiKey: apiKey - ? `${apiKey.slice(0, 6)}${"*".repeat(Math.max(0, apiKey.length - 6))}` - : "", - hasApiKey: !!apiKey, - apiBaseUrl, - }); - } catch (err) { - authLogger.error("Failed to get Tailscale settings", err); - res.status(500).json({ error: "Failed to get Tailscale settings" }); - } - }); - - /** - * @openapi - * /users/tailscale-settings: - * patch: - * summary: Update Tailscale settings (admin only) - * description: Saves or clears the Tailscale API key used for device discovery. - * tags: - * - Users - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * apiKey: - * type: string - * apiBaseUrl: - * type: string - * description: Optional custom control-plane API base URL (e.g. a Headscale instance). Leave empty to use the Tailscale default. - * responses: - * 200: - * description: Tailscale settings updated. - * 403: - * description: Not authorized. - * 500: - * description: Failed to update Tailscale settings. - */ - router.patch("/tailscale-settings", authenticateJWT, async (req, res) => { - const userId = (req as AuthenticatedRequest).userId; - try { - const actor = await getAdminActor(userId); - if (!actor) { - return res.status(403).json({ error: "Not authorized" }); - } - const { apiKey, apiBaseUrl } = req.body; - if (typeof apiKey !== "string") { - return res.status(400).json({ error: "apiKey must be a string" }); - } - if (apiBaseUrl !== undefined && typeof apiBaseUrl !== "string") { - return res.status(400).json({ error: "apiBaseUrl must be a string" }); - } - const settingsRepo = createCurrentSettingsRepository(); - await settingsRepo.set("tailscale_api_key", apiKey); - if (apiBaseUrl !== undefined) { - await settingsRepo.set( - "tailscale_api_base_url", - apiBaseUrl.trim().replace(/\/+$/, ""), - ); - } - - const { ipAddress, userAgent } = getRequestMeta(req); - await logAudit({ - userId, - username: actor.username ?? userId, - action: "update_tailscale_settings", - resourceType: "setting", - details: JSON.stringify({ hasApiKey: !!apiKey }), - ipAddress, - userAgent, - success: true, - }); - - res.json({ hasApiKey: !!apiKey }); - } catch (err) { - authLogger.error("Failed to update Tailscale settings", err); - res.status(500).json({ error: "Failed to update Tailscale settings" }); - } - }); - /** * @openapi * /users/command-history-enabled: diff --git a/src/backend/plugins/ctx.ts b/src/backend/plugins/ctx.ts index d2e664ffe..5674c8373 100644 --- a/src/backend/plugins/ctx.ts +++ b/src/backend/plugins/ctx.ts @@ -36,6 +36,7 @@ import { } from "./http.js"; import { registerPluginWsRoute, registerPluginWsUpgrade } from "./ws.js"; import { resolvePermission } from "./rbac.js"; +import * as pluginSettings from "./settings.js"; import type { PluginManifest } from "@termix/plugin-sdk/manifest"; import { PluginCapabilityError, @@ -257,6 +258,18 @@ export function createPluginContext( { action: "db_client" }, ); + // The one settings call that leaves the plugin's own namespace, so the one + // that needs a capability and an audit line. + const settingsReadCore = guarded( + manifest, + "settings:read-core", + async (key: string) => pluginSettings.readCoreSetting(key), + { + action: "settings_read_core", + details: () => "read a core server setting", + }, + ); + const dbRefs = guarded( manifest, "db:own", @@ -474,6 +487,66 @@ export function createPluginContext( }, }, + settings: { + // Reading and writing a plugin's OWN settings is ungated. The manifest + // already declares every field, and a plugin that had to ask permission + // to read its own configuration would be useless. Only readCore, which + // reaches outside the plugin's namespace, is a capability. + get: (key) => + pluginSettings.getSetting(manifest, "admin", null, key) as never, + set: async (key, value) => { + const error = await pluginSettings.setSetting( + manifest, + "admin", + null, + key, + value, + ); + if (error) throw new Error(error); + }, + + getUser: (userId, key) => + pluginSettings.getSetting(manifest, "user", userId, key) as never, + setUser: async (userId, key, value) => { + const error = await pluginSettings.setSetting( + manifest, + "user", + userId, + key, + value, + ); + if (error) throw new Error(error); + }, + + getHost: (hostId, key) => + pluginSettings.getSetting(manifest, "host", hostId, key) as never, + setHost: async (hostId, key, value) => { + const error = await pluginSettings.setSetting( + manifest, + "host", + hostId, + key, + value, + ); + if (error) throw new Error(error); + }, + + getAll: (scope, scopeId) => + pluginSettings.getAllSettings(manifest, scope, scopeId ?? null), + + onChange: (key, listener) => { + const unsubscribe = pluginSettings.onSettingsChange( + pluginId, + key, + listener, + ); + handle.bag.add(unsubscribe, `settings listener for "${key}"`); + return unsubscribe; + }, + + readCore: (key) => settingsReadCore(key), + }, + rbac: { // A short name takes this plugin's prefix; another plugin's id or a core // group is used as given, which is what makes a cross-plugin check @@ -623,6 +696,7 @@ export async function disposePluginHandle( // Belt and braces: an offer made outside ctx, or one whose registration was // lost to a throw mid-activate, would otherwise outlive the plugin. secretRegistry.withdrawAllForPlugin(pluginId); + pluginSettings.clearSettingsListeners(pluginId); // Skipped when activate() never finished: a plugin's deactivate expects the // state activate builds, and calling it on a half-built plugin tends to diff --git a/src/backend/plugins/settings.ts b/src/backend/plugins/settings.ts new file mode 100644 index 000000000..bc9275ff2 --- /dev/null +++ b/src/backend/plugins/settings.ts @@ -0,0 +1,319 @@ +/** + * Plugin settings: validation, storage, encryption and change notification. + * + * One module behind both ctx.settings and the /plugins/:id/settings routes, so + * "what is a valid value", "which fields are secret" and "what a read hands + * back" are decided once. Two implementations would drift, and the one that + * drifted would be the one writing to the database. + * + * A field the manifest never declared is rejected rather than stored. That is + * what stops a PUT from filling a plugin's namespace with arbitrary keys, and + * it is why every entry point resolves the field first and the value second. + */ + +import type { + PluginManifest, + PluginSettingsField, +} from "@termix/plugin-sdk/manifest"; +import { + coerceSettingValue, + isRedactedSecret, + validateSettingValue, +} from "@termix/plugin-sdk/settings"; +import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; +import type { PluginSettingsScope } from "../database/repositories/plugin-settings-repository.js"; +import { + decryptSystemSecret, + encryptSystemSecret, +} from "../utils/system-secret-crypto.js"; +import { pluginLogger } from "../utils/logger.js"; + +export type { PluginSettingsScope }; + +/** + * Core settings a plugin may read through ctx.settings.readCore. + * + * Deliberately short. Everything here is either already visible in the UI to + * every user, or a switch a plugin has to honour to behave correctly. Adding + * to it means deciding that every plugin holding settings:read-core may see + * that value, so it is a review decision rather than a convenience. + */ +export const CORE_SETTINGS_ALLOWLIST: readonly string[] = [ + "app_name", + "guac_enabled", + "global_metrics_interval", + "global_status_check_interval", +]; + +type ChangeListener = (value: unknown) => void; + +/** pluginId -> key -> listeners. In-process only; settings are not clustered. */ +const listeners = new Map>>(); + +export function onSettingsChange( + pluginId: string, + key: string, + listener: ChangeListener, +): () => void { + let byKey = listeners.get(pluginId); + if (!byKey) { + byKey = new Map(); + listeners.set(pluginId, byKey); + } + let set = byKey.get(key); + if (!set) { + set = new Set(); + byKey.set(key, set); + } + set.add(listener); + + return () => { + set!.delete(listener); + if (set!.size === 0) byKey!.delete(key); + if (byKey!.size === 0) listeners.delete(pluginId); + }; +} + +function notify(pluginId: string, key: string, value: unknown): void { + for (const listener of listeners.get(pluginId)?.get(key) ?? []) { + try { + listener(value); + } catch (error) { + // A listener throwing must not fail the write that already happened. + pluginLogger.error( + `Plugin ${pluginId} settings listener for "${key}" failed`, + error instanceof Error ? error : new Error(String(error)), + { operation: "plugin_settings_change" }, + ); + } + } +} + +/** Drops every listener a plugin registered. Called when it deactivates. */ +export function clearSettingsListeners(pluginId: string): void { + listeners.delete(pluginId); +} + +/** The fields a manifest declares for one scope. */ +export function declaredFields( + manifest: PluginManifest, + scope: PluginSettingsScope, +): PluginSettingsField[] { + const settings = manifest.contributes?.settings; + if (!settings) return []; + + if (scope === "admin") return settings.admin ?? []; + if (scope === "user") return settings.user ?? []; + + const host = settings.host; + if (!host) return []; + + // The enable switch is a real stored key, so it has to behave like a field + // everywhere: validated on write, defaulted on read, rendered in the editor. + const enableField: PluginSettingsField[] = host.enableKey + ? [ + { + key: host.enableKey, + type: "boolean", + labelKey: host.enableLabelKey, + default: false, + }, + ] + : []; + + return [...enableField, ...host.fields]; +} + +export function findField( + manifest: PluginManifest, + scope: PluginSettingsScope, + key: string, +): PluginSettingsField | undefined { + return declaredFields(manifest, scope).find((field) => field.key === key); +} + +function normalizeScopeId( + scope: PluginSettingsScope, + scopeId: string | number | undefined | null, +): string | null { + if (scope === "admin") return null; + if (scopeId === undefined || scopeId === null) { + throw new Error(`Plugin settings scope "${scope}" requires a scope id`); + } + return String(scopeId); +} + +/** Stored values are JSON so a field keeps its declared type on the way back. */ +function encode(value: unknown): string { + return JSON.stringify(value ?? null); +} + +function decode(raw: string | null): unknown { + if (raw === null) return undefined; + try { + return JSON.parse(raw); + } catch { + return undefined; + } +} + +export async function getSetting( + manifest: PluginManifest, + scope: PluginSettingsScope, + scopeId: string | number | null | undefined, + key: string, +): Promise { + const field = findField(manifest, scope, key); + if (!field) return undefined; + + const row = await createCurrentPluginSettingsRepository().get( + manifest.id, + scope, + normalizeScopeId(scope, scopeId), + key, + ); + + if (!row || row.value === null) return field.default; + + const decoded = decode(row.value); + if (decoded === undefined || decoded === null) return field.default; + + if (row.encrypted && typeof decoded === "string") { + return decryptSystemSecret(decoded); + } + return decoded; +} + +export interface SetSettingOptions { + /** Skips validation for a value core itself produced, such as a migration. */ + trusted?: boolean; +} + +/** + * Writes one value, after validating it against the declared field. + * + * Returns the error message when the value is rejected, or null on success, + * rather than throwing: a PUT reports every bad field at once and a throw per + * field would only ever surface the first. + */ +export async function setSetting( + manifest: PluginManifest, + scope: PluginSettingsScope, + scopeId: string | number | null | undefined, + key: string, + value: unknown, + options: SetSettingOptions = {}, +): Promise { + const field = findField(manifest, scope, key); + if (!field) { + return `"${key}" is not a settings field this plugin declares`; + } + + // Echoing a redacted secret back means "leave it alone", so a form can save + // without clearing a key it was never shown. + if (field.type === "secret" && isRedactedSecret(value)) return null; + + const coerced = options.trusted ? value : coerceSettingValue(field, value); + if (!options.trusted) { + const error = validateSettingValue(field, coerced); + if (error) return error; + } + + // Clearing a secret writes null rather than an encrypted empty string, so a + // read reports it as unset instead of "set to nothing". + const clearingSecret = field.type === "secret" && coerced === ""; + const shouldEncrypt = + field.type === "secret" && typeof coerced === "string" && !clearingSecret; + const stored = clearingSecret + ? null + : shouldEncrypt + ? encode(await encryptSystemSecret(coerced as string)) + : encode(coerced); + + await createCurrentPluginSettingsRepository().set( + manifest.id, + scope, + normalizeScopeId(scope, scopeId), + key, + stored, + shouldEncrypt, + ); + + notify(manifest.id, key, coerced); + return null; +} + +export interface GetAllOptions { + /** Replaces every secret with { set } so the value never leaves the server. */ + redactSecrets?: boolean; +} + +/** + * Every declared field for one scope, with stored values over defaults. + * + * Driven by the manifest rather than by the rows, so a field that was never + * written still comes back with its default and a stale row for a field the + * plugin has since dropped is ignored instead of being handed to the UI. + */ +export async function getAllSettings( + manifest: PluginManifest, + scope: PluginSettingsScope, + scopeId: string | number | null | undefined, + options: GetAllOptions = {}, +): Promise> { + const fields = declaredFields(manifest, scope); + if (fields.length === 0) return {}; + + const rows = await createCurrentPluginSettingsRepository().getAll( + manifest.id, + scope, + normalizeScopeId(scope, scopeId), + ); + const byKey = new Map(rows.map((row) => [row.key, row])); + + const values: Record = {}; + for (const field of fields) { + values[field.key] = await resolveFieldValue( + field, + byKey.get(field.key), + options, + ); + } + return values; +} + +/** Shared by getAllSettings and the host-list projection. */ +export async function resolveFieldValue( + field: PluginSettingsField, + row: { value: string | null; encrypted: boolean } | undefined, + options: GetAllOptions = {}, +): Promise { + const decoded = row && row.value !== null ? decode(row.value) : undefined; + const hasValue = decoded !== undefined && decoded !== null && decoded !== ""; + + if (field.type === "secret") { + if (options.redactSecrets) return { set: hasValue }; + if (!hasValue) return field.default; + return row!.encrypted && typeof decoded === "string" + ? decryptSystemSecret(decoded) + : decoded; + } + + return hasValue ? decoded : field.default; +} + +/** + * Reads a core server setting. The caller has already checked + * settings:read-core; this only enforces the allowlist. + */ +export async function readCoreSetting(key: string): Promise { + if (!CORE_SETTINGS_ALLOWLIST.includes(key)) { + throw new Error( + `Core setting "${key}" is not readable by plugins. Readable keys: ${CORE_SETTINGS_ALLOWLIST.join(", ")}`, + ); + } + + const { createCurrentSettingsRepository } = + await import("../database/repositories/factory.js"); + return createCurrentSettingsRepository().get(key); +} diff --git a/src/backend/starter.ts b/src/backend/starter.ts index 5bda1a562..45c802022 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -344,6 +344,12 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { operation: "plugin_init", }); } + + // After seeding: plugin_settings has a foreign key to plugins, so there + // is nothing to migrate into until the plugin row exists. + const { runTailscaleSettingsMigration } = + await import("./utils/crypto-migration/tailscale-settings-migration.js"); + await runTailscaleSettingsMigration(); } catch (error) { systemLogger.warn("Plugin runtime failed to initialize", { operation: "plugin_init", diff --git a/src/backend/tests/database/plugin-settings-cascade.test.ts b/src/backend/tests/database/plugin-settings-cascade.test.ts new file mode 100644 index 000000000..39890bde2 --- /dev/null +++ b/src/backend/tests/database/plugin-settings-cascade.test.ts @@ -0,0 +1,147 @@ +/** + * plugin_settings cleanup when a user or host goes. + * + * scope_id is polymorphic, so it carries no foreign key and the engine will + * not cascade it. The repositories delete these rows explicitly, and these + * tests exist so a later refactor that drops those calls fails here rather + * than leaving orphaned settings behind forever. + */ + +import { asc } from "drizzle-orm"; +import { afterEach, describe, expect, it } from "vitest"; +import { TestSqliteDatabase } from "./repositories/test-support.js"; +import { HostRepository } from "../../database/repositories/host-repository.js"; +import { UserRepository } from "../../database/repositories/user-repository.js"; +import type { DatabaseContext } from "../../database/repositories/database-context.js"; +import { pluginSettings } from "../../database/db/schema.js"; + +let adapter: TestSqliteDatabase | null = null; +let ctx: DatabaseContext | null = null; + +afterEach(async () => { + if (adapter) { + await adapter.close(); + adapter = null; + ctx = null; + } +}); + +async function seed(): Promise { + adapter = new TestSqliteDatabase(); + const context = await adapter.connect(); + ctx = context; + + 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) + VALUES + (7, 'user-1', 'seven', '10.0.0.7', 22, 'root', 'password'), + (8, 'user-1', 'eight', '10.0.0.8', 22, 'root', 'password'), + (9, 'user-2', 'nine', '10.0.0.9', 22, 'root', 'password'); + + INSERT INTO plugins (id, name, version, manifest_json) + VALUES ('sample', 'Sample', '1.0.0', '{}'); + `); + + return context; +} + +async function addSetting( + scope: string, + scopeId: string | null, + key = "k", +): Promise { + await adapter!.exec( + `INSERT INTO plugin_settings (plugin_id, scope, scope_id, key, value) + VALUES ('sample', '${scope}', ${scopeId === null ? "NULL" : `'${scopeId}'`}, '${key}', '"v"')`, + ); +} + +async function remaining(): Promise< + { scope: string; scopeId: string | null }[] +> { + const rows = await ctx!.drizzle + .select({ + scope: pluginSettings.scope, + scopeId: pluginSettings.scopeId, + }) + .from(pluginSettings) + .orderBy(asc(pluginSettings.scope), asc(pluginSettings.scopeId)); + return rows; +} + +describe("user deletion", () => { + it("removes that user's plugin settings", async () => { + const context = await seed(); + await addSetting("user", "user-1"); + + await new UserRepository(context).delete("user-1"); + + expect(await remaining()).toEqual([]); + }); + + it("leaves another user's settings alone", async () => { + const context = await seed(); + await addSetting("user", "user-1"); + await addSetting("user", "user-2"); + + await new UserRepository(context).delete("user-1"); + + expect(await remaining()).toEqual([{ scope: "user", scopeId: "user-2" }]); + }); + + it("leaves admin-scope settings alone", async () => { + const context = await seed(); + await addSetting("admin", null); + await addSetting("user", "user-1"); + + await new UserRepository(context).delete("user-1"); + + expect(await remaining()).toEqual([{ scope: "admin", scopeId: null }]); + }); +}); + +describe("host deletion", () => { + it("removes that host's plugin settings", async () => { + const context = await seed(); + await addSetting("host", "7"); + + await new HostRepository(context).deleteForUser("user-1", 7); + + expect(await remaining()).toEqual([]); + }); + + it("leaves another host's settings alone", async () => { + const context = await seed(); + await addSetting("host", "7"); + await addSetting("host", "8"); + + await new HostRepository(context).deleteForUser("user-1", 7); + + expect(await remaining()).toEqual([{ scope: "host", scopeId: "8" }]); + }); + + it("clears settings for every host in the bulk delete path", async () => { + const context = await seed(); + await addSetting("host", "7"); + await addSetting("host", "8"); + await addSetting("host", "9"); + + await new HostRepository(context).deleteByUserId("user-1"); + + // Host 9 belongs to another user and keeps its settings. + expect(await remaining()).toEqual([{ scope: "host", scopeId: "9" }]); + }); + + it("does not confuse a host id with a user id", async () => { + const context = await seed(); + await addSetting("host", "7"); + await addSetting("user", "7"); + + await new HostRepository(context).deleteForUser("user-1", 7); + + expect(await remaining()).toEqual([{ scope: "user", scopeId: "7" }]); + }); +}); diff --git a/src/backend/tests/database/routes/host-plugin-settings.test.ts b/src/backend/tests/database/routes/host-plugin-settings.test.ts new file mode 100644 index 000000000..f04ae5741 --- /dev/null +++ b/src/backend/tests/database/routes/host-plugin-settings.test.ts @@ -0,0 +1,244 @@ +/** + * The pluginSettings map attached to host responses. + * + * The host editor needs these with the host, not one request per plugin per + * host, so the shape and the query count both matter. + */ + +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; + +interface Row { + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; + encrypted: boolean; +} + +const rows: Row[] = []; +const loaded: { id: string; manifest: PluginManifest; state: string }[] = []; +const getAllForScopeIds = vi.fn(async (scope: string, scopeIds: string[]) => + rows.filter( + (row) => row.scope === scope && scopeIds.includes(row.scopeId ?? ""), + ), +); + +vi.mock("../../../utils/logger.js", () => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + success: vi.fn(), + }; + return { sshLogger: logger, pluginLogger: logger, databaseLogger: logger }; +}); + +vi.mock("../../../database/repositories/factory.js", () => ({ + createCurrentPluginSettingsRepository: () => ({ getAllForScopeIds }), +})); + +vi.mock("../../../plugins/index.js", () => ({ + getPluginRuntime: () => ({ loader: { list: () => loaded } }), +})); + +vi.mock("../../../utils/system-secret-crypto.js", () => ({ + isSystemEncrypted: (value: string) => value.startsWith("sysenc:v1:"), + encryptSystemSecret: async (plaintext: string) => plaintext, + decryptSystemSecret: async (stored: string) => stored, +})); + +const { + attachHostPluginSettings, + loadHostPluginSettings, + withHostPluginSettings, +} = await import("../../../database/routes/host-plugin-settings.js"); + +function manifest(id: string, host: unknown): PluginManifest { + return { + id, + name: id, + version: "1.0.0", + description: "", + author: { name: "t" }, + license: "MIT", + category: "Productivity", + engine: { termix: ">=2.9.0", api: "1" }, + capabilities: [], + contributes: { settings: { host } }, + } as PluginManifest; +} + +const DOCKER = manifest("docker", { + enableKey: "enableDocker", + enableLabelKey: "k", + fields: [{ key: "socketPath", type: "string", labelKey: "k" }], +}); + +beforeEach(() => { + rows.length = 0; + loaded.length = 0; + getAllForScopeIds.mockClear(); +}); + +describe("loadHostPluginSettings", () => { + it("returns nothing when no plugin declares host settings", async () => { + loaded.push({ + id: "other", + manifest: manifest("other", undefined), + state: "active", + }); + + const result = await loadHostPluginSettings([1, 2]); + + expect(result.size).toBe(0); + expect(getAllForScopeIds).not.toHaveBeenCalled(); + }); + + it("ignores a plugin that is not running", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "disabled" }); + + const result = await loadHostPluginSettings([1]); + + expect(result.size).toBe(0); + }); + + it("uses one query for the whole list", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + + await loadHostPluginSettings([1, 2, 3, 4, 5]); + + expect(getAllForScopeIds).toHaveBeenCalledTimes(1); + expect(getAllForScopeIds).toHaveBeenCalledWith("host", [ + "1", + "2", + "3", + "4", + "5", + ]); + }); + + it("merges stored values over declared defaults", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + rows.push({ + pluginId: "docker", + scope: "host", + scopeId: "1", + key: "enableDocker", + value: JSON.stringify(true), + encrypted: false, + }); + + const result = await loadHostPluginSettings([1, 2]); + + expect(result.get(1)).toEqual({ + docker: { enableDocker: true, socketPath: undefined }, + }); + // Host 2 has nothing stored, so it falls back to the declared default. + expect(result.get(2)).toEqual({ + docker: { enableDocker: false, socketPath: undefined }, + }); + }); + + it("keeps one host's values out of another's", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + rows.push({ + pluginId: "docker", + scope: "host", + scopeId: "1", + key: "socketPath", + value: JSON.stringify("/var/run/docker.sock"), + encrypted: false, + }); + + const result = await loadHostPluginSettings([1, 2]); + + expect(result.get(1)!.docker.socketPath).toBe("/var/run/docker.sock"); + expect(result.get(2)!.docker.socketPath).toBeUndefined(); + }); + + it("redacts a host-scope secret", async () => { + loaded.push({ + id: "vault", + manifest: manifest("vault", { + fields: [{ key: "token", type: "secret", labelKey: "k" }], + }), + state: "active", + }); + rows.push({ + pluginId: "vault", + scope: "host", + scopeId: "1", + key: "token", + value: JSON.stringify("s.verysecret"), + encrypted: true, + }); + + const result = await loadHostPluginSettings([1]); + + expect(result.get(1)!.vault.token).toEqual({ set: true }); + expect(JSON.stringify(result.get(1))).not.toContain("verysecret"); + }); + + it("survives a failed settings read rather than failing the host list", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + getAllForScopeIds.mockRejectedValueOnce(new Error("database is away")); + + await expect(loadHostPluginSettings([1])).resolves.toEqual(new Map()); + }); + + it("does nothing for an empty host list", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + + expect((await loadHostPluginSettings([])).size).toBe(0); + expect(getAllForScopeIds).not.toHaveBeenCalled(); + }); +}); + +describe("attachHostPluginSettings", () => { + it("attaches values to the matching host", () => { + const hosts = [{ id: 1 }, { id: 2 }] as Record[]; + const settings = new Map([[1, { docker: { enableDocker: true } }]]); + + attachHostPluginSettings(hosts, settings); + + expect(hosts[0].pluginSettings).toEqual({ docker: { enableDocker: true } }); + expect(hosts[1].pluginSettings).toBeUndefined(); + }); + + it("leaves hosts alone when there is nothing to attach", () => { + const hosts = [{ id: 1 }] as Record[]; + + attachHostPluginSettings(hosts, new Map()); + + expect(hosts[0]).toEqual({ id: 1 }); + }); +}); + +describe("withHostPluginSettings", () => { + it("adds the map to a single host", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + + const result = await withHostPluginSettings({ id: 1, name: "web" }); + + expect(result.name).toBe("web"); + expect(result.pluginSettings).toEqual({ + docker: { enableDocker: false, socketPath: undefined }, + }); + }); + + it("returns the host unchanged when nothing contributes", async () => { + const host = { id: 1, name: "web" }; + + expect(await withHostPluginSettings(host)).toBe(host); + }); + + it("returns the host unchanged when its id is not a number", async () => { + loaded.push({ id: "docker", manifest: DOCKER, state: "active" }); + const host = { name: "web" }; + + expect(await withHostPluginSettings(host)).toBe(host); + }); +}); diff --git a/src/backend/tests/plugins/settings-manifest.test.ts b/src/backend/tests/plugins/settings-manifest.test.ts new file mode 100644 index 000000000..1b3138b91 --- /dev/null +++ b/src/backend/tests/plugins/settings-manifest.test.ts @@ -0,0 +1,356 @@ +/** + * contributes.settings validation. + * + * A settings field is the one thing in a manifest that core writes to the + * database on a plugin's behalf, so the schema has to reject anything it would + * not know how to store or render. + */ + +import { describe, expect, it } from "vitest"; +import { + parseManifest, + validateManifest, + SUPPORTED_PLUGIN_API_VERSION, +} from "../../plugins/manifest.js"; + +function base(overrides: Record = {}) { + return { + id: "sample-plugin", + name: "Sample Plugin", + version: "1.0.0", + description: "A fixture.", + author: { name: "Termix Tests" }, + license: "MIT", + category: "Productivity", + engine: { termix: ">=2.9.0", api: SUPPORTED_PLUGIN_API_VERSION }, + capabilities: ["kv:own"], + ...overrides, + }; +} + +function withSettings(settings: unknown) { + return base({ contributes: { settings } }); +} + +describe("contributes.settings", () => { + it("accepts a manifest with every field type", () => { + const errors = validateManifest( + withSettings({ + admin: [ + { key: "apiKey", type: "secret", labelKey: "k" }, + { key: "baseUrl", type: "string", labelKey: "k", default: "" }, + { key: "retries", type: "number", labelKey: "k", min: 0, max: 5 }, + { key: "enabled", type: "boolean", labelKey: "k" }, + { + key: "mode", + type: "select", + labelKey: "k", + options: [{ value: "a", labelKey: "k" }], + }, + { + key: "tags", + type: "multiselect", + labelKey: "k", + options: [{ value: "a", labelKey: "k" }], + }, + { key: "notes", type: "textarea", labelKey: "k" }, + { key: "raw", type: "json", labelKey: "k" }, + { key: "browser", type: "custom", component: "devices" }, + ], + user: [{ key: "theme", type: "string", labelKey: "k" }], + host: { + enableKey: "enableThing", + enableLabelKey: "k", + fields: [{ key: "port", type: "number", labelKey: "k" }], + }, + }), + ); + + expect(errors).toEqual([]); + }); + + it("accepts a manifest with no settings at all", () => { + expect(validateManifest(base())).toEqual([]); + }); + + it("rejects an unknown field property", () => { + const errors = validateManifest( + withSettings({ + admin: [{ key: "a", type: "string", labelKey: "k", wat: 1 }], + }), + ); + + expect(errors.join(" ")).toContain('Unknown field "wat"'); + }); + + it("rejects an unknown scope", () => { + const errors = validateManifest( + withSettings({ global: [{ key: "a", type: "string", labelKey: "k" }] }), + ); + + expect(errors.join(" ")).toContain('Unknown field "global"'); + }); + + it("rejects an unknown field type", () => { + const errors = validateManifest( + withSettings({ admin: [{ key: "a", type: "colour", labelKey: "k" }] }), + ); + + expect(errors.join(" ")).toContain("must be one of"); + }); + + it("rejects a duplicate key within one scope", () => { + const errors = validateManifest( + withSettings({ + admin: [ + { key: "a", type: "string", labelKey: "k" }, + { key: "a", type: "number", labelKey: "k" }, + ], + }), + ); + + expect(errors.join(" ")).toContain('duplicates "a"'); + }); + + it("allows the same key in two different scopes", () => { + const errors = validateManifest( + withSettings({ + admin: [{ key: "shared", type: "string", labelKey: "k" }], + user: [{ key: "shared", type: "string", labelKey: "k" }], + }), + ); + + expect(errors).toEqual([]); + }); + + it("requires labelKey for everything but a custom field", () => { + expect( + validateManifest( + withSettings({ admin: [{ key: "a", type: "string" }] }), + ).join(" "), + ).toContain("labelKey"); + + expect( + validateManifest( + withSettings({ admin: [{ key: "a", type: "custom", component: "x" }] }), + ), + ).toEqual([]); + }); + + it("requires component on a custom field and forbids it elsewhere", () => { + expect( + validateManifest( + withSettings({ admin: [{ key: "a", type: "custom" }] }), + ).join(" "), + ).toContain("component"); + + expect( + validateManifest( + withSettings({ + admin: [{ key: "a", type: "string", labelKey: "k", component: "x" }], + }), + ).join(" "), + ).toContain('only valid when type is "custom"'); + }); + + it("requires non-empty options on select and multiselect", () => { + for (const type of ["select", "multiselect"]) { + expect( + validateManifest( + withSettings({ admin: [{ key: "a", type, labelKey: "k" }] }), + ).join(" "), + ).toContain("options must be a non-empty array"); + } + }); + + it("rejects options on a field that has no options", () => { + const errors = validateManifest( + withSettings({ + admin: [ + { + key: "a", + type: "string", + labelKey: "k", + options: [{ value: "x", labelKey: "k" }], + }, + ], + }), + ); + + expect(errors.join(" ")).toContain("only valid for select and multiselect"); + }); + + it("rejects min greater than max", () => { + const errors = validateManifest( + withSettings({ + admin: [{ key: "a", type: "number", labelKey: "k", min: 10, max: 1 }], + }), + ); + + expect(errors.join(" ")).toContain("must not be greater than"); + }); + + it("rejects min or max on a non-number field", () => { + const errors = validateManifest( + withSettings({ + admin: [{ key: "a", type: "string", labelKey: "k", min: 1 }], + }), + ); + + expect(errors.join(" ")).toContain("only valid for number fields"); + }); + + it("rejects requires that names no boolean in the same scope", () => { + const errors = validateManifest( + withSettings({ + admin: [ + { key: "a", type: "string", labelKey: "k", requires: "missing" }, + ], + }), + ); + + expect(errors.join(" ")).toContain("must name a boolean field"); + }); + + it("rejects requires that names a non-boolean field", () => { + const errors = validateManifest( + withSettings({ + admin: [ + { key: "flag", type: "string", labelKey: "k" }, + { key: "a", type: "string", labelKey: "k", requires: "flag" }, + ], + }), + ); + + expect(errors.join(" ")).toContain("must name a boolean field"); + }); + + it("accepts requires pointing at a boolean declared later", () => { + const errors = validateManifest( + withSettings({ + admin: [ + { key: "a", type: "string", labelKey: "k", requires: "flag" }, + { key: "flag", type: "boolean", labelKey: "k" }, + ], + }), + ); + + expect(errors).toEqual([]); + }); + + it("lets a host field require the section's enable key", () => { + const errors = validateManifest( + withSettings({ + host: { + enableKey: "enableThing", + enableLabelKey: "k", + fields: [ + { + key: "port", + type: "number", + labelKey: "k", + requires: "enableThing", + }, + ], + }, + }), + ); + + expect(errors).toEqual([]); + }); + + it("rejects a host field colliding with the enable key", () => { + const errors = validateManifest( + withSettings({ + host: { + enableKey: "enableThing", + enableLabelKey: "k", + fields: [{ key: "enableThing", type: "boolean", labelKey: "k" }], + }, + }), + ); + + expect(errors.join(" ")).toContain("already the section's enableKey"); + }); + + it("requires enableLabelKey alongside enableKey", () => { + const errors = validateManifest( + withSettings({ + host: { enableKey: "enableThing", fields: [] }, + }), + ); + + expect(errors.join(" ")).toContain("enableLabelKey"); + }); + + it("rejects a key that would not be index-safe", () => { + const errors = validateManifest( + withSettings({ + admin: [{ key: "a".repeat(80), type: "string", labelKey: "k" }], + }), + ); + + expect(errors.join(" ")).toContain("short alphanumeric key"); + }); +}); + +describe("settings field permissions", () => { + it("rejects a short permission the plugin does not declare", () => { + const { errors } = parseManifest( + base({ + contributes: { + settings: { + user: [ + { key: "a", type: "string", labelKey: "k", permission: "use" }, + ], + }, + }, + }), + ); + + expect(errors.join(" ")).toContain( + "not declared in contributes.permissions", + ); + }); + + it("accepts a short permission the plugin declares", () => { + const { errors, manifest } = parseManifest( + base({ + contributes: { + permissions: [{ name: "use", titleKey: "k", descriptionKey: "k" }], + settings: { + user: [ + { key: "a", type: "string", labelKey: "k", permission: "use" }, + ], + }, + }, + }), + ); + + expect(errors).toEqual([]); + expect(manifest?.contributes?.settings?.user?.[0].permission).toBe("use"); + }); + + it("accepts a fully qualified permission from another namespace", () => { + // Cross-namespace ids cannot be checked here: the validator has no idea + // which other plugins exist. Taken as given, exactly like ctx.rbac does. + const { errors } = parseManifest( + base({ + contributes: { + settings: { + user: [ + { + key: "a", + type: "string", + labelKey: "k", + permission: "hosts.view", + }, + ], + }, + }, + }), + ); + + expect(errors).toEqual([]); + }); +}); diff --git a/src/backend/tests/plugins/settings-routes.test.ts b/src/backend/tests/plugins/settings-routes.test.ts new file mode 100644 index 000000000..de689c280 --- /dev/null +++ b/src/backend/tests/plugins/settings-routes.test.ts @@ -0,0 +1,498 @@ +/** + * The /plugins/:id/settings routes. + * + * These decide who may change what, so the tests are mostly about refusal: a + * non-admin must not write install-wide configuration, a user must not write + * another user's settings or a host they cannot edit, and a secret must never + * come back over the wire. + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import express from "express"; +import type { AddressInfo } from "node:net"; +import type { Server } from "node:http"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; + +const state = vi.hoisted(() => ({ + userId: "user-1", + permissions: new Set(), + hostEditAccess: true, +})); + +vi.mock("../../utils/logger.js", () => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + success: vi.fn(), + }; + return { pluginLogger: logger, databaseLogger: logger, apiLogger: logger }; +}); + +vi.mock("../../utils/auth-manager.js", () => ({ + AuthManager: { + getInstance: () => ({ + createAuthMiddleware: + () => + ( + req: express.Request, + res: express.Response, + next: express.NextFunction, + ) => { + (req as express.Request & { userId?: string }).userId = state.userId; + next(); + }, + }), + }, +})); + +vi.mock("../../utils/permission-manager.js", () => ({ + PermissionManager: { + getInstance: () => ({ + hasPermission: async (_userId: string, permission: string) => + state.permissions.has(permission), + requirePermission: + (permission: string) => + ( + _req: express.Request, + res: express.Response, + next: express.NextFunction, + ) => { + if (!state.permissions.has(permission)) { + res.status(403).json({ error: "Insufficient permissions" }); + return; + } + next(); + }, + canAccessHost: async () => ({ + hasAccess: state.hostEditAccess, + isOwner: state.hostEditAccess, + isShared: false, + }), + }), + }, +})); + +vi.mock("../../utils/audit-logger.js", () => ({ + logAudit: vi.fn(async () => {}), + getAuditUsername: vi.fn(async (id: string) => id), + getRequestMeta: () => ({ ipAddress: "127.0.0.1", userAgent: "test" }), +})); + +const MANIFEST: PluginManifest = { + id: "sample", + name: "Sample", + version: "1.0.0", + description: "", + author: { name: "t" }, + license: "MIT", + category: "Productivity", + engine: { termix: ">=2.9.0", api: "1" }, + capabilities: [], + contributes: { + permissions: [{ name: "tweak", titleKey: "k", descriptionKey: "k" }], + settings: { + admin: [ + { key: "apiKey", type: "secret", labelKey: "k" }, + { key: "retries", type: "number", labelKey: "k", min: 0, max: 5 }, + { + key: "narrowed", + type: "string", + labelKey: "k", + permission: "tweak", + }, + ], + user: [ + { key: "theme", type: "string", labelKey: "k", default: "dark" }, + { + key: "advanced", + type: "string", + labelKey: "k", + permission: "tweak", + }, + ], + host: { + enableKey: "enableThing", + enableLabelKey: "k", + fields: [{ key: "port", type: "number", labelKey: "k" }], + }, + }, + }, +} as PluginManifest; + +vi.mock("../../plugins/index.js", () => ({ + getPluginRuntime: () => ({ + loader: { + get: (id: string) => + id === "sample" + ? { id, manifest: MANIFEST, state: "active" } + : undefined, + list: () => [{ id: "sample", manifest: MANIFEST, state: "active" }], + }, + }), +})); + +interface Row { + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; + encrypted: boolean; +} + +const rows: Row[] = []; + +function find(scope: string, scopeId: string | null, key: string) { + return rows.find( + (row) => row.scope === scope && row.scopeId === scopeId && row.key === key, + ); +} + +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentPluginRepository: () => ({ + findById: async (id: string) => + id === "sample" + ? { id, manifestJson: JSON.stringify(MANIFEST), state: "enabled" } + : null, + listAll: async () => [], + }), + createCurrentPluginPermissionGrantRepository: () => ({ + listByPlugin: async () => [], + }), + createCurrentPluginSettingsRepository: () => ({ + get: async ( + _pluginId: string, + scope: string, + scopeId: string | null, + key: string, + ) => find(scope, scopeId, key) ?? null, + getAll: async (_pluginId: string, scope: string, scopeId: string | null) => + rows.filter((row) => row.scope === scope && row.scopeId === scopeId), + set: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + value: string | null, + encrypted = false, + ) => { + const existing = find(scope, scopeId, key); + if (existing) { + existing.value = value; + existing.encrypted = encrypted; + return; + } + rows.push({ pluginId, scope, scopeId, key, value, encrypted }); + }, + }), + createCurrentSettingsRepository: () => ({ get: async () => null }), +})); + +vi.mock("../../utils/system-secret-crypto.js", () => ({ + isSystemEncrypted: (value: string) => value.startsWith("sysenc:v1:"), + encryptSystemSecret: async (plaintext: string) => + !plaintext || plaintext.startsWith("sysenc:v1:") + ? plaintext + : `sysenc:v1:${Buffer.from(plaintext, "utf8").toString("base64")}`, + decryptSystemSecret: async (stored: string) => + stored.startsWith("sysenc:v1:") + ? Buffer.from(stored.slice("sysenc:v1:".length), "base64").toString( + "utf8", + ) + : stored, +})); + +const pluginRoutes = (await import("../../database/routes/plugins.js")).default; + +let server: Server; +let baseUrl: string; + +beforeEach(async () => { + rows.length = 0; + state.userId = "user-1"; + state.permissions = new Set(); + state.hostEditAccess = true; + + const app = express(); + app.use(express.json()); + app.use("/plugins", pluginRoutes); + + server = await new Promise((resolve) => { + const created = app.listen(0, "127.0.0.1", () => resolve(created)); + }); + baseUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; +}); + +afterEach(async () => { + await new Promise((resolve) => server.close(() => resolve())); +}); + +function call(path: string, init?: RequestInit) { + return fetch(`${baseUrl}${path}`, { + ...init, + headers: { "content-type": "application/json", ...(init?.headers ?? {}) }, + }); +} + +describe("admin scope", () => { + it("refuses a reader without admin.plugins.manage", async () => { + const response = await call("/plugins/sample/settings/admin"); + + expect(response.status).toBe(403); + }); + + it("refuses a writer without admin.plugins.manage", async () => { + const response = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ retries: 2 }), + }); + + expect(response.status).toBe(403); + expect(rows).toHaveLength(0); + }); + + it("lets an admin read and write", async () => { + state.permissions.add("admin.plugins.manage"); + + const write = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ retries: 3 }), + }); + expect(write.status).toBe(200); + + const read = await call("/plugins/sample/settings/admin"); + expect((await read.json()).values.retries).toBe(3); + }); + + it("reports a rejected value per field", async () => { + state.permissions.add("admin.plugins.manage"); + + const response = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ retries: 99 }), + }); + + expect(response.status).toBe(400); + expect((await response.json()).errors.retries).toContain("at most 5"); + expect(rows).toHaveLength(0); + }); + + it("refuses a key the manifest never declared", async () => { + state.permissions.add("admin.plugins.manage"); + + const response = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ somethingElse: "x" }), + }); + + expect(response.status).toBe(400); + expect((await response.json()).errors.somethingElse).toBeDefined(); + expect(rows).toHaveLength(0); + }); + + it("never returns a secret's value", async () => { + state.permissions.add("admin.plugins.manage"); + + await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ apiKey: "tskey-very-secret" }), + }); + + const response = await call("/plugins/sample/settings/admin"); + const body = await response.text(); + + expect(body).not.toContain("tskey-very-secret"); + expect(JSON.parse(body).values.apiKey).toEqual({ set: true }); + }); + + it("treats a redacted echo as no change", async () => { + state.permissions.add("admin.plugins.manage"); + + await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ apiKey: "tskey-original" }), + }); + await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ apiKey: { set: true }, retries: 1 }), + }); + + const row = rows.find((entry) => entry.key === "apiKey")!; + expect( + Buffer.from( + JSON.parse(row.value!).slice("sysenc:v1:".length), + "base64", + ).toString("utf8"), + ).toBe("tskey-original"); + }); + + it("keeps the admin gate even for a field naming its own permission", async () => { + // Holding the field's permission is not enough: admin scope is admin + // scope, so a plugin cannot hand install-wide config to an ordinary user. + state.permissions.add("sample.tweak"); + + const response = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ narrowed: "x" }), + }); + + expect(response.status).toBe(403); + expect(rows).toHaveLength(0); + }); + + it("narrows an admin field to holders of its permission", async () => { + state.permissions.add("admin.plugins.manage"); + + const denied = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ narrowed: "x" }), + }); + expect(denied.status).toBe(400); + expect((await denied.json()).errors.narrowed).toContain("permission"); + + state.permissions.add("sample.tweak"); + const allowed = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify({ narrowed: "x" }), + }); + expect(allowed.status).toBe(200); + }); + + it("404s for a plugin that does not exist", async () => { + state.permissions.add("admin.plugins.manage"); + + const response = await call("/plugins/nope/settings/admin"); + + expect(response.status).toBe(404); + }); + + it("rejects a body that is not an object", async () => { + state.permissions.add("admin.plugins.manage"); + + const response = await call("/plugins/sample/settings/admin", { + method: "PUT", + body: JSON.stringify([1, 2, 3]), + }); + + expect(response.status).toBe(400); + }); +}); + +describe("user scope", () => { + it("lets any authenticated user read and write their own", async () => { + const write = await call("/plugins/sample/settings/user", { + method: "PUT", + body: JSON.stringify({ theme: "light" }), + }); + expect(write.status).toBe(200); + + const read = await call("/plugins/sample/settings/user"); + expect((await read.json()).values.theme).toBe("light"); + }); + + it("scopes writes to the caller, never to a user id in the body", async () => { + await call("/plugins/sample/settings/user", { + method: "PUT", + body: JSON.stringify({ theme: "light", userId: "someone-else" }), + }); + + // The bogus key is rejected and the row belongs to the caller. + expect(rows.every((row) => row.scopeId === "user-1")).toBe(true); + + state.userId = "user-2"; + const other = await call("/plugins/sample/settings/user"); + expect((await other.json()).values.theme).toBe("dark"); + }); + + it("refuses a user field gated on a permission the caller lacks", async () => { + const response = await call("/plugins/sample/settings/user", { + method: "PUT", + body: JSON.stringify({ advanced: "x" }), + }); + + expect(response.status).toBe(400); + expect((await response.json()).errors.advanced).toContain("permission"); + expect(rows).toHaveLength(0); + }); + + it("allows it once the caller holds that permission", async () => { + state.permissions.add("sample.tweak"); + + const response = await call("/plugins/sample/settings/user", { + method: "PUT", + body: JSON.stringify({ advanced: "x" }), + }); + + expect(response.status).toBe(200); + }); + + it("refuses an admin field through the user route", async () => { + const response = await call("/plugins/sample/settings/user", { + method: "PUT", + body: JSON.stringify({ apiKey: "nope" }), + }); + + expect(response.status).toBe(400); + expect(rows).toHaveLength(0); + }); +}); + +describe("host scope", () => { + it("lets a user with edit access read and write", async () => { + const write = await call("/plugins/sample/settings/host/7", { + method: "PUT", + body: JSON.stringify({ enableThing: true, port: 22 }), + }); + expect(write.status).toBe(200); + + const read = await call("/plugins/sample/settings/host/7"); + const values = (await read.json()).values; + expect(values.enableThing).toBe(true); + expect(values.port).toBe(22); + }); + + it("refuses a host the caller cannot edit", async () => { + state.hostEditAccess = false; + + const read = await call("/plugins/sample/settings/host/7"); + expect(read.status).toBe(403); + + const write = await call("/plugins/sample/settings/host/7", { + method: "PUT", + body: JSON.stringify({ port: 22 }), + }); + expect(write.status).toBe(403); + expect(rows).toHaveLength(0); + }); + + it("rejects an invalid host id", async () => { + const response = await call("/plugins/sample/settings/host/not-a-number"); + + expect(response.status).toBe(400); + }); + + it("keeps each host's values apart", async () => { + await call("/plugins/sample/settings/host/7", { + method: "PUT", + body: JSON.stringify({ port: 22 }), + }); + await call("/plugins/sample/settings/host/8", { + method: "PUT", + body: JSON.stringify({ port: 2222 }), + }); + + const seven = await (await call("/plugins/sample/settings/host/7")).json(); + const eight = await (await call("/plugins/sample/settings/host/8")).json(); + + expect(seven.values.port).toBe(22); + expect(eight.values.port).toBe(2222); + }); + + it("returns the declared default for an untouched host", async () => { + const response = await call("/plugins/sample/settings/host/9"); + + expect((await response.json()).values.enableThing).toBe(false); + }); +}); diff --git a/src/backend/tests/plugins/settings.test.ts b/src/backend/tests/plugins/settings.test.ts new file mode 100644 index 000000000..d7f2ed8f6 --- /dev/null +++ b/src/backend/tests/plugins/settings.test.ts @@ -0,0 +1,442 @@ +/** + * The plugin settings service. + * + * Storage is faked at the repository boundary; everything above it - schema + * lookup, validation, encryption, redaction and change notification - is the + * real implementation, because that is where the behaviour worth protecting + * lives. + */ + +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; + +interface Row { + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; + encrypted: boolean; +} + +const rows: Row[] = []; + +function find( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, +) { + return rows.find( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId && + row.key === key, + ); +} + +const settingsRepository = { + get: vi.fn( + async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + ) => find(pluginId, scope, scopeId, key) ?? null, + ), + getAll: vi.fn( + async (pluginId: string, scope: string, scopeId: string | null) => + rows.filter( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId, + ), + ), + set: vi.fn( + async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + value: string | null, + encrypted = false, + ) => { + const existing = find(pluginId, scope, scopeId, key); + if (existing) { + existing.value = value; + existing.encrypted = encrypted; + return; + } + rows.push({ pluginId, scope, scopeId, key, value, encrypted }); + }, + ), +}; + +const coreSettings = new Map(); + +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentPluginSettingsRepository: () => settingsRepository, + createCurrentSettingsRepository: () => ({ + get: async (key: string) => coreSettings.get(key) ?? null, + }), +})); + +// Real AES would need the system key on disk. The prefix and the idempotence +// are what the service depends on, so the double keeps both. +vi.mock("../../utils/system-secret-crypto.js", () => ({ + isSystemEncrypted: (value: string) => value.startsWith("sysenc:v1:"), + encryptSystemSecret: async (plaintext: string) => + !plaintext || plaintext.startsWith("sysenc:v1:") + ? plaintext + : `sysenc:v1:${Buffer.from(plaintext, "utf8").toString("base64")}`, + decryptSystemSecret: async (stored: string) => + stored.startsWith("sysenc:v1:") + ? Buffer.from(stored.slice("sysenc:v1:".length), "base64").toString( + "utf8", + ) + : stored, +})); + +const { + CORE_SETTINGS_ALLOWLIST, + clearSettingsListeners, + declaredFields, + findField, + getAllSettings, + getSetting, + onSettingsChange, + readCoreSetting, + setSetting, +} = await import("../../plugins/settings.js"); + +function manifest(settings: unknown): PluginManifest { + return { + id: "sample", + name: "Sample", + version: "1.0.0", + description: "", + author: { name: "t" }, + license: "MIT", + category: "Productivity", + engine: { termix: ">=2.9.0", api: "1" }, + capabilities: [], + contributes: { settings }, + } as PluginManifest; +} + +const ADMIN = manifest({ + admin: [ + { key: "apiKey", type: "secret", labelKey: "k" }, + { key: "baseUrl", type: "string", labelKey: "k", default: "https://x" }, + { key: "retries", type: "number", labelKey: "k", min: 0, max: 5 }, + { key: "enabled", type: "boolean", labelKey: "k", default: false }, + { + key: "mode", + type: "select", + labelKey: "k", + options: [ + { value: "fast", labelKey: "k" }, + { value: "slow", labelKey: "k" }, + ], + }, + ], +}); + +beforeEach(() => { + rows.length = 0; + coreSettings.clear(); + clearSettingsListeners("sample"); + vi.clearAllMocks(); +}); + +describe("declaredFields", () => { + it("returns the fields for each scope", () => { + expect(declaredFields(ADMIN, "admin").map((f) => f.key)).toEqual([ + "apiKey", + "baseUrl", + "retries", + "enabled", + "mode", + ]); + expect(declaredFields(ADMIN, "user")).toEqual([]); + }); + + it("treats the host enable key as a real boolean field", () => { + const withHost = manifest({ + host: { + enableKey: "enableThing", + enableLabelKey: "k", + fields: [{ key: "port", type: "number", labelKey: "k" }], + }, + }); + + const fields = declaredFields(withHost, "host"); + expect(fields.map((f) => f.key)).toEqual(["enableThing", "port"]); + expect(fields[0]).toMatchObject({ type: "boolean", default: false }); + }); + + it("finds a field by key", () => { + expect(findField(ADMIN, "admin", "baseUrl")?.type).toBe("string"); + expect(findField(ADMIN, "admin", "nope")).toBeUndefined(); + }); +}); + +describe("setSetting", () => { + it("writes a valid value and reads it back", async () => { + expect( + await setSetting(ADMIN, "admin", null, "baseUrl", "https://y"), + ).toBeNull(); + expect(await getSetting(ADMIN, "admin", null, "baseUrl")).toBe("https://y"); + }); + + it("refuses a key the manifest never declared", async () => { + const error = await setSetting(ADMIN, "admin", null, "wat", "x"); + + expect(error).toContain("not a settings field this plugin declares"); + expect(rows).toHaveLength(0); + }); + + it("refuses a value that fails the declared constraint", async () => { + expect(await setSetting(ADMIN, "admin", null, "retries", 99)).toContain( + "at most 5", + ); + expect( + await setSetting(ADMIN, "admin", null, "mode", "sideways"), + ).toContain("must be one of"); + expect(rows).toHaveLength(0); + }); + + it("coerces the strings a form actually sends", async () => { + expect(await setSetting(ADMIN, "admin", null, "retries", "3")).toBeNull(); + expect(await getSetting(ADMIN, "admin", null, "retries")).toBe(3); + + expect( + await setSetting(ADMIN, "admin", null, "enabled", "true"), + ).toBeNull(); + expect(await getSetting(ADMIN, "admin", null, "enabled")).toBe(true); + }); + + it("keeps each scope separate", async () => { + const scoped = manifest({ + admin: [{ key: "v", type: "string", labelKey: "k" }], + user: [{ key: "v", type: "string", labelKey: "k" }], + host: { fields: [{ key: "v", type: "string", labelKey: "k" }] }, + }); + + await setSetting(scoped, "admin", null, "v", "a"); + await setSetting(scoped, "user", "user-1", "v", "u"); + await setSetting(scoped, "host", 7, "v", "h"); + + expect(await getSetting(scoped, "admin", null, "v")).toBe("a"); + expect(await getSetting(scoped, "user", "user-1", "v")).toBe("u"); + expect(await getSetting(scoped, "host", 7, "v")).toBe("h"); + // A different user sees nothing of the first user's value. + expect(await getSetting(scoped, "user", "user-2", "v")).toBeUndefined(); + }); + + it("stores a host id as text, matching the polymorphic scope column", async () => { + const scoped = manifest({ + host: { fields: [{ key: "v", type: "string", labelKey: "k" }] }, + }); + + await setSetting(scoped, "host", 12, "v", "x"); + + expect(rows[0].scopeId).toBe("12"); + // Either spelling resolves to the same row. + expect(await getSetting(scoped, "host", "12", "v")).toBe("x"); + }); +}); + +describe("defaults", () => { + it("falls back to the declared default when nothing is stored", async () => { + expect(await getSetting(ADMIN, "admin", null, "baseUrl")).toBe("https://x"); + expect(await getSetting(ADMIN, "admin", null, "enabled")).toBe(false); + }); + + it("returns undefined for a field with no default and no value", async () => { + expect(await getSetting(ADMIN, "admin", null, "retries")).toBeUndefined(); + }); + + it("returns undefined for a key the manifest does not declare", async () => { + expect(await getSetting(ADMIN, "admin", null, "wat")).toBeUndefined(); + }); +}); + +describe("secrets", () => { + it("encrypts on the way in and decrypts on the way out", async () => { + await setSetting(ADMIN, "admin", null, "apiKey", "tskey-secret"); + + const row = rows.find((entry) => entry.key === "apiKey")!; + expect(row.encrypted).toBe(true); + expect(row.value).not.toContain("tskey-secret"); + expect(JSON.parse(row.value!)).toMatch(/^sysenc:v1:/); + + expect(await getSetting(ADMIN, "admin", null, "apiKey")).toBe( + "tskey-secret", + ); + }); + + it("redacts a secret rather than returning it", async () => { + await setSetting(ADMIN, "admin", null, "apiKey", "tskey-secret"); + + const values = await getAllSettings(ADMIN, "admin", null, { + redactSecrets: true, + }); + + expect(values.apiKey).toEqual({ set: true }); + expect(JSON.stringify(values)).not.toContain("tskey-secret"); + }); + + it("reports an unset secret as set:false", async () => { + const values = await getAllSettings(ADMIN, "admin", null, { + redactSecrets: true, + }); + + expect(values.apiKey).toEqual({ set: false }); + }); + + it("treats a redacted echo as leave-it-alone", async () => { + await setSetting(ADMIN, "admin", null, "apiKey", "original"); + + expect( + await setSetting(ADMIN, "admin", null, "apiKey", { set: true }), + ).toBeNull(); + + expect(await getSetting(ADMIN, "admin", null, "apiKey")).toBe("original"); + }); + + it("lets a secret be replaced and cleared", async () => { + await setSetting(ADMIN, "admin", null, "apiKey", "first"); + await setSetting(ADMIN, "admin", null, "apiKey", "second"); + expect(await getSetting(ADMIN, "admin", null, "apiKey")).toBe("second"); + + await setSetting(ADMIN, "admin", null, "apiKey", ""); + const values = await getAllSettings(ADMIN, "admin", null, { + redactSecrets: true, + }); + expect(values.apiKey).toEqual({ set: false }); + }); + + it("hands a plugin its own secret in the clear", async () => { + await setSetting(ADMIN, "admin", null, "apiKey", "tskey-secret"); + + const values = await getAllSettings(ADMIN, "admin", null); + + expect(values.apiKey).toBe("tskey-secret"); + }); +}); + +describe("getAllSettings", () => { + it("is driven by the manifest, not by the stored rows", async () => { + await setSetting(ADMIN, "admin", null, "baseUrl", "https://y"); + // A row for a field the plugin has since dropped. + rows.push({ + pluginId: "sample", + scope: "admin", + scopeId: null, + key: "removedField", + value: JSON.stringify("stale"), + encrypted: false, + }); + + const values = await getAllSettings(ADMIN, "admin", null); + + expect(values.baseUrl).toBe("https://y"); + expect(values.enabled).toBe(false); + expect(values).not.toHaveProperty("removedField"); + }); + + it("returns nothing for a scope the plugin declares no fields in", async () => { + expect(await getAllSettings(ADMIN, "user", "user-1")).toEqual({}); + }); +}); + +describe("onSettingsChange", () => { + it("fires on a successful write", async () => { + const listener = vi.fn(); + onSettingsChange("sample", "baseUrl", listener); + + await setSetting(ADMIN, "admin", null, "baseUrl", "https://z"); + + expect(listener).toHaveBeenCalledWith("https://z"); + }); + + it("does not fire for a rejected write", async () => { + const listener = vi.fn(); + onSettingsChange("sample", "retries", listener); + + await setSetting(ADMIN, "admin", null, "retries", 99); + + expect(listener).not.toHaveBeenCalled(); + }); + + it("stops firing once unsubscribed", async () => { + const listener = vi.fn(); + const unsubscribe = onSettingsChange("sample", "baseUrl", listener); + + unsubscribe(); + await setSetting(ADMIN, "admin", null, "baseUrl", "https://z"); + + expect(listener).not.toHaveBeenCalled(); + }); + + it("only notifies listeners for that key and plugin", async () => { + const other = vi.fn(); + const another = vi.fn(); + onSettingsChange("sample", "enabled", other); + onSettingsChange("different-plugin", "baseUrl", another); + + await setSetting(ADMIN, "admin", null, "baseUrl", "https://z"); + + expect(other).not.toHaveBeenCalled(); + expect(another).not.toHaveBeenCalled(); + clearSettingsListeners("different-plugin"); + }); + + it("survives a listener that throws", async () => { + const good = vi.fn(); + onSettingsChange("sample", "baseUrl", () => { + throw new Error("listener blew up"); + }); + onSettingsChange("sample", "baseUrl", good); + + await expect( + setSetting(ADMIN, "admin", null, "baseUrl", "https://z"), + ).resolves.toBeNull(); + expect(good).toHaveBeenCalled(); + }); + + it("drops every listener when the plugin deactivates", async () => { + const listener = vi.fn(); + onSettingsChange("sample", "baseUrl", listener); + + clearSettingsListeners("sample"); + await setSetting(ADMIN, "admin", null, "baseUrl", "https://z"); + + expect(listener).not.toHaveBeenCalled(); + }); +}); + +describe("readCoreSetting", () => { + it("returns an allowlisted key", async () => { + coreSettings.set("app_name", "Termix"); + + expect(await readCoreSetting("app_name")).toBe("Termix"); + }); + + it("refuses a key that is not on the allowlist", async () => { + coreSettings.set("jwt_secret", "nope"); + + await expect(readCoreSetting("jwt_secret")).rejects.toThrow( + /not readable by plugins/, + ); + }); + + it("does not expose anything sensitive through the allowlist", () => { + for (const key of CORE_SETTINGS_ALLOWLIST) { + expect(key).not.toMatch(/secret|password|token|key$/i); + } + }); +}); diff --git a/src/backend/tests/utils/tailscale-settings-migration.test.ts b/src/backend/tests/utils/tailscale-settings-migration.test.ts new file mode 100644 index 000000000..2f6b451d1 --- /dev/null +++ b/src/backend/tests/utils/tailscale-settings-migration.test.ts @@ -0,0 +1,228 @@ +/** + * The Tailscale settings migration. + * + * An upgrade must be lossless: an install that had a working API key must keep + * working without anyone re-entering it. That is the whole point of the test, + * and running it twice proves a restart cannot corrupt what it moved. + */ + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +interface Row { + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; + encrypted: boolean; +} + +const coreSettings = new Map(); +const pluginRows: Row[] = []; +let pluginInstalled = true; + +const pluginSettingsRepository = { + get: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + ) => + pluginRows.find( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId && + row.key === key, + ) ?? null, + set: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + value: string | null, + encrypted = false, + ) => { + const existing = pluginRows.find( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId && + row.key === key, + ); + if (existing) { + existing.value = value; + existing.encrypted = encrypted; + return; + } + pluginRows.push({ pluginId, scope, scopeId, key, value, encrypted }); + }, +}; + +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentPluginRepository: () => ({ + findById: async (id: string) => + pluginInstalled && id === "tailscale" ? { id } : null, + }), + createCurrentPluginSettingsRepository: () => pluginSettingsRepository, + createCurrentSettingsRepository: () => ({ + get: async (key: string) => coreSettings.get(key) ?? null, + delete: async (key: string) => coreSettings.delete(key), + }), +})); + +vi.mock("../../utils/system-secret-crypto.js", () => ({ + isSystemEncrypted: (value: string) => value.startsWith("sysenc:v1:"), + encryptSystemSecret: async (plaintext: string) => + !plaintext || plaintext.startsWith("sysenc:v1:") + ? plaintext + : `sysenc:v1:${Buffer.from(plaintext, "utf8").toString("base64")}`, + decryptSystemSecret: async (stored: string) => + stored.startsWith("sysenc:v1:") + ? Buffer.from(stored.slice("sysenc:v1:".length), "base64").toString( + "utf8", + ) + : stored, +})); + +const { runTailscaleSettingsMigration } = + await import("../../utils/crypto-migration/tailscale-settings-migration.js"); + +function storedValue(key: string): unknown { + const row = pluginRows.find((entry) => entry.key === key); + return row?.value === null || row?.value === undefined + ? undefined + : JSON.parse(row.value); +} + +beforeEach(() => { + coreSettings.clear(); + pluginRows.length = 0; + pluginInstalled = true; +}); + +describe("runTailscaleSettingsMigration", () => { + it("moves an existing key and base URL into plugin settings", async () => { + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + coreSettings.set("tailscale_api_base_url", "https://headscale.example"); + + const result = await runTailscaleSettingsMigration(); + + expect(result.moved).toEqual([ + "tailscale_api_key", + "tailscale_api_base_url", + ]); + expect(storedValue("apiKey")).toMatch(/^sysenc:v1:/); + expect(storedValue("apiBaseUrl")).toBe("https://headscale.example"); + }); + + it("encrypts the key, which was stored in plaintext before", async () => { + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + + await runTailscaleSettingsMigration(); + + const row = pluginRows.find((entry) => entry.key === "apiKey")!; + expect(row.encrypted).toBe(true); + expect(row.value).not.toContain("tskey-api-legacy"); + }); + + it("leaves the base URL readable, since it is not a secret", async () => { + coreSettings.set("tailscale_api_base_url", "https://headscale.example"); + + await runTailscaleSettingsMigration(); + + const row = pluginRows.find((entry) => entry.key === "apiBaseUrl")!; + expect(row.encrypted).toBe(false); + }); + + it("removes the legacy rows so no plaintext key is left behind", async () => { + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + coreSettings.set("tailscale_api_base_url", "https://headscale.example"); + + await runTailscaleSettingsMigration(); + + expect(coreSettings.has("tailscale_api_key")).toBe(false); + expect(coreSettings.has("tailscale_api_base_url")).toBe(false); + }); + + it("is idempotent: a second run changes nothing", async () => { + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + coreSettings.set("tailscale_api_base_url", "https://headscale.example"); + + await runTailscaleSettingsMigration(); + const afterFirst = JSON.parse(JSON.stringify(pluginRows)); + + const second = await runTailscaleSettingsMigration(); + + expect(second.moved).toEqual([]); + expect(pluginRows).toEqual(afterFirst); + }); + + it("cannot double-encrypt the key across runs", async () => { + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + + await runTailscaleSettingsMigration(); + const first = storedValue("apiKey"); + + await runTailscaleSettingsMigration(); + + expect(storedValue("apiKey")).toBe(first); + expect(String(storedValue("apiKey")).match(/sysenc:v1:/g)).toHaveLength(1); + }); + + it("drops a stale legacy row when the value was already moved", async () => { + // An interrupted run, or a downgrade that rewrote the old key. + await pluginSettingsRepository.set( + "tailscale", + "admin", + null, + "apiKey", + JSON.stringify("sysenc:v1:already"), + true, + ); + coreSettings.set("tailscale_api_key", "tskey-plaintext-leftover"); + + const result = await runTailscaleSettingsMigration(); + + expect(result.skipped).toContain("tailscale_api_key"); + expect(storedValue("apiKey")).toBe("sysenc:v1:already"); + expect(coreSettings.has("tailscale_api_key")).toBe(false); + }); + + it("does nothing on a fresh install with no legacy rows", async () => { + const result = await runTailscaleSettingsMigration(); + + expect(result.moved).toEqual([]); + expect(pluginRows).toEqual([]); + }); + + it("skips an empty legacy value rather than storing one", async () => { + coreSettings.set("tailscale_api_key", ""); + + const result = await runTailscaleSettingsMigration(); + + expect(result.moved).toEqual([]); + expect(pluginRows).toEqual([]); + }); + + it("waits for the plugin row, because of the foreign key", async () => { + pluginInstalled = false; + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + + const result = await runTailscaleSettingsMigration(); + + expect(result.moved).toEqual([]); + expect(pluginRows).toEqual([]); + // Still there for the next boot, once the plugin is seeded. + expect(coreSettings.get("tailscale_api_key")).toBe("tskey-api-legacy"); + }); + + it("moves each key independently", async () => { + coreSettings.set("tailscale_api_key", "tskey-api-legacy"); + + const result = await runTailscaleSettingsMigration(); + + expect(result.moved).toEqual(["tailscale_api_key"]); + expect(storedValue("apiBaseUrl")).toBeUndefined(); + }); +}); diff --git a/src/backend/utils/crypto-migration/tailscale-settings-migration.ts b/src/backend/utils/crypto-migration/tailscale-settings-migration.ts new file mode 100644 index 000000000..8beb1ed18 --- /dev/null +++ b/src/backend/utils/crypto-migration/tailscale-settings-migration.ts @@ -0,0 +1,105 @@ +/** + * Moves the Tailscale API key and base URL out of core settings. + * + * Both lived in the core `settings` table, written by a core route that existed + * only to serve one plugin. A6 gives plugins their own settings, so they belong + * in plugin_settings under the tailscale plugin. + * + * The key was stored in plaintext, and the route that read it back masked only + * its tail while being open to any authenticated user. Landing it as a secret + * field encrypts it at rest, so this migration is a fix as much as a move. + * + * Idempotent in two ways: it stops when a value is already present in the new + * home, and encryptSystemSecret returns an already-encrypted value unchanged, + * so a second pass can neither duplicate nor double-encrypt. + */ + +import { databaseLogger } from "../logger.js"; +import { + createCurrentPluginRepository, + createCurrentPluginSettingsRepository, + createCurrentSettingsRepository, +} from "../../database/repositories/factory.js"; +import { encryptSystemSecret } from "../system-secret-crypto.js"; + +const PLUGIN_ID = "tailscale"; + +/** Legacy core key -> the field the plugin now declares. */ +const MOVES: { legacyKey: string; field: string; secret: boolean }[] = [ + { legacyKey: "tailscale_api_key", field: "apiKey", secret: true }, + { legacyKey: "tailscale_api_base_url", field: "apiBaseUrl", secret: false }, +]; + +export interface TailscaleSettingsMigrationResult { + moved: string[]; + skipped: string[]; +} + +export async function runTailscaleSettingsMigration(): Promise { + const moved: string[] = []; + const skipped: string[] = []; + + try { + // The FK to plugins means there is nothing to migrate into until the + // plugin has been seeded. Not an error: a build without it just has no + // tailscale rows. + const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); + if (!plugin) return { moved, skipped }; + + const settingsRepository = createCurrentSettingsRepository(); + const pluginSettingsRepository = createCurrentPluginSettingsRepository(); + + for (const move of MOVES) { + const legacyValue = await settingsRepository.get(move.legacyKey); + if (legacyValue === null || legacyValue === "") { + continue; + } + + const existing = await pluginSettingsRepository.get( + PLUGIN_ID, + "admin", + null, + move.field, + ); + if (existing && existing.value !== null) { + // Already moved. Drop the stale copy rather than leaving a plaintext + // key behind for a reader that predates this change. + await settingsRepository.delete(move.legacyKey); + skipped.push(move.legacyKey); + continue; + } + + const stored = move.secret + ? await encryptSystemSecret(legacyValue) + : legacyValue; + + await pluginSettingsRepository.set( + PLUGIN_ID, + "admin", + null, + move.field, + JSON.stringify(stored), + move.secret, + ); + + await settingsRepository.delete(move.legacyKey); + moved.push(move.legacyKey); + } + + if (moved.length > 0) { + databaseLogger.info( + `Moved ${moved.length} Tailscale setting(s) into plugin settings`, + { operation: "tailscale_settings_migration", moved: moved.join(", ") }, + ); + } + } catch (error) { + // A failed migration must not stop the backend. The plugin falls back to + // "no key configured", which is a visible, recoverable state. + databaseLogger.warn("Tailscale settings migration failed", { + operation: "tailscale_settings_migration", + error: error instanceof Error ? error.message : String(error), + }); + } + + return { moved, skipped }; +} diff --git a/src/types/ui-types.ts b/src/types/ui-types.ts index 44d205b90..20d04a5e2 100644 --- a/src/types/ui-types.ts +++ b/src/types/ui-types.ts @@ -185,6 +185,8 @@ export type Host = { hasTelnetPassword?: boolean; guacamoleConfig?: GuacamoleConfig; + /** Host-scope plugin settings, keyed by plugin id. Secrets are redacted. */ + pluginSettings?: Record>; forceKeyboardInteractive?: boolean; isShared?: boolean; @@ -372,8 +374,7 @@ export type AdminSection = | "audit-log" | "ssl" | "plugins" - | "touch-input" - | "tailscale"; + | "touch-input"; export type ThemeId = | "dark" | "light" diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index c5c47b816..365a74c1a 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -126,14 +126,11 @@ const SessionLogsPanel = lazy(() => default: m.SessionLogsPanel, })), ); -const UserProfilePanel = lazy(() => - import("@/sidebar/UserProfilePanel").then((m) => ({ - default: m.UserProfilePanel, - })), -); -const AdminSettingsPanel = lazy(() => - import("@/sidebar/AdminSettingsPanel").then((m) => ({ - default: m.AdminSettingsPanel, +// One screen behind both rail entries. It lazy-loads the profile and admin +// panels itself, so the shell no longer imports either directly. +const SettingsScreen = lazy(() => + import("@/settings/SettingsScreen").then((m) => ({ + default: m.SettingsScreen, })), ); const CredentialsPanel = lazy(() => @@ -2882,9 +2879,14 @@ export function AppShell({ )} - {railView === "user-profile" && ( -
- + setRemoteSyncReconnectRequested(false) } - /> -
- )} - - {railView === "admin-settings" && showAdminUI && ( -
- { connectHost(host); diff --git a/src/ui/api/plugins-api.ts b/src/ui/api/plugins-api.ts index 5d7dd6637..a6f508a45 100644 --- a/src/ui/api/plugins-api.ts +++ b/src/ui/api/plugins-api.ts @@ -7,8 +7,153 @@ export interface PluginTabContribution { openFrom: string[]; } +export type PluginSettingsFieldType = + | "boolean" + | "string" + | "number" + | "select" + | "multiselect" + | "secret" + | "textarea" + | "json" + | "custom"; + +export interface PluginSettingsField { + key: string; + type: PluginSettingsFieldType; + labelKey?: string; + descriptionKey?: string; + placeholderKey?: string; + default?: unknown; + options?: { value: string; labelKey: string }[]; + min?: number; + max?: number; + requires?: string; + permission?: string; + group?: string; + component?: string; +} + +export interface PluginHostSettingsContribution { + enableKey?: string; + enableLabelKey?: string; + fields: PluginSettingsField[]; +} + +export interface PluginSettingsContribution { + admin?: PluginSettingsField[]; + user?: PluginSettingsField[]; + host?: PluginHostSettingsContribution; +} + export interface PluginContributions { tabs?: PluginTabContribution[]; + settings?: PluginSettingsContribution; +} + +/** A secret as it arrives from the server. The value never leaves the server. */ +export interface RedactedSecret { + set: boolean; +} + +export function isRedactedSecret(value: unknown): value is RedactedSecret { + return ( + typeof value === "object" && + value !== null && + !Array.isArray(value) && + typeof (value as RedactedSecret).set === "boolean" + ); +} + +export type PluginSettingsValues = Record; + +/** Per-field messages from a rejected PUT, keyed by field key. */ +export interface PluginSettingsErrors { + [key: string]: string; +} + +export class PluginSettingsValidationError extends Error { + readonly errors: PluginSettingsErrors; + + constructor(errors: PluginSettingsErrors) { + super("Some settings were rejected"); + this.name = "PluginSettingsValidationError"; + this.errors = errors; + } +} + +function settingsPath(pluginId: string, suffix: string): string { + return `/plugins/${encodeURIComponent(pluginId)}/settings/${suffix}`; +} + +/** Turns a 400 carrying per-field errors into something a form can render. */ +async function putSettings( + path: string, + values: PluginSettingsValues, +): Promise { + try { + const response = await rbacApi.put(path, values); + return response.data?.values ?? {}; + } catch (error) { + const data = ( + error as { response?: { status?: number; data?: { errors?: unknown } } } + ).response; + if (data?.status === 400 && data.data?.errors) { + throw new PluginSettingsValidationError( + data.data.errors as PluginSettingsErrors, + ); + } + throw error; + } +} + +export async function getPluginAdminSettings( + pluginId: string, +): Promise { + const response = await rbacApi.get(settingsPath(pluginId, "admin")); + return response.data?.values ?? {}; +} + +export async function updatePluginAdminSettings( + pluginId: string, + values: PluginSettingsValues, +): Promise { + return putSettings(settingsPath(pluginId, "admin"), values); +} + +export async function getPluginUserSettings( + pluginId: string, +): Promise { + const response = await rbacApi.get(settingsPath(pluginId, "user")); + return response.data?.values ?? {}; +} + +export async function updatePluginUserSettings( + pluginId: string, + values: PluginSettingsValues, +): Promise { + return putSettings(settingsPath(pluginId, "user"), values); +} + +export async function getPluginHostSettings( + pluginId: string, + hostId: number, +): Promise { + const response = await rbacApi.get( + settingsPath(pluginId, `host/${encodeURIComponent(String(hostId))}`), + ); + return response.data?.values ?? {}; +} + +export async function updatePluginHostSettings( + pluginId: string, + hostId: number, + values: PluginSettingsValues, +): Promise { + return putSettings( + settingsPath(pluginId, `host/${encodeURIComponent(String(hostId))}`), + values, + ); } /** @@ -26,6 +171,8 @@ export interface PluginSummary { /** enabled | disabled | blocked | failed, or the loader's live state. */ state: string; contributes: PluginContributions | null; + /** Lucide icon name from the manifest. */ + icon?: string; tier?: string; source?: string; diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 487984bf7..df589f1db 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -3508,7 +3508,6 @@ "sectionApiKeys": "API Keys", "sectionAuditLog": "Audit Log", "sectionSsl": "SSL / Let's Encrypt", - "sectionTailscale": "Tailscale", "sectionTouchInput": "Touch Input", "touchEnabled": "Enable Touch Input", "touchEnabledDesc": "Allow one-finger touch scrolling in terminals", @@ -3667,11 +3666,6 @@ "enableRemoteDesktopDesc": "RDP/VNC remote desktop", "enableRemoteDesktopDocsLink": "View docs", "guacdUrl": "guacd URL", - "tailscaleApiKey": "Tailscale API Key", - "tailscaleApiKeyDescription": "Used for device discovery in the host editor. Supports Tailscale (tskey-) and Headscale (hskey-) keys. Generate a key at tailscale.com/admin/settings/keys or from your Headscale instance.", - "tailscaleApiKeyDocsLink": "View docs", - "tailscaleApiBaseUrl": "API Base URL", - "tailscaleApiBaseUrlDescription": "Optional. Point to a self-hosted Headscale instance instead of api.tailscale.com. Leave empty to use the Tailscale default.", "oidcDescription": "Configure OpenID Connect for SSO. Fields marked * are required.", "oidcDocsLink": "View docs", "oidcClientId": "Client ID", @@ -3822,8 +3816,6 @@ "remoteDesktopSaved": "Remote Desktop settings saved", "remoteDesktopSaveFailed": "Failed to save Remote Desktop settings", "remoteDesktopUpdateFailed": "Failed to update Remote Desktop setting", - "tailscaleSettingsSaved": "Tailscale settings saved", - "tailscaleSettingsSaveFailed": "Failed to save Tailscale settings", "logLevelUpdateFailed": "Failed to update log level", "oidcSaved": "OIDC configuration saved", "oidcSaveFailed": "Failed to save OIDC config", @@ -5261,7 +5253,52 @@ "title": "View tailnet devices", "description": "See the devices on the connected tailnet." } + }, + "settings": { + "group": { + "api": "Tailscale API", + "devices": "Tailnet" + }, + "apiKey": { + "label": "API key", + "description": "Used to list the devices on your tailnet. Works with a Tailscale or Headscale key.", + "placeholder": "tskey-api-... / hskey-api-..." + }, + "apiBaseUrl": { + "label": "API base URL", + "description": "Leave empty for Tailscale. Set this to point at a Headscale instance.", + "placeholder": "https://api.tailscale.com/api/v2" + }, + "devices": { + "label": "Device list", + "checking": "Checking the tailnet...", + "noKey": "No API key set yet.", + "error": "Could not reach the tailnet. Check the API key.", + "count": "{{count}} device reachable", + "count_other": "{{count}} devices reachable", + "refresh": "Check" + } } } + }, + "settings": { + "title": "Settings", + "bandYou": "You", + "bandPlugins": "Plugins", + "bandInstance": "Instance", + "account": "Account", + "secretSet": "Set", + "secretNotSet": "Not set", + "secretReplace": "Replace", + "pluginGroupAdmin": "Server settings", + "pluginGroupUser": "Your settings", + "pluginNoSettings": "{{name}} has no settings you can change.", + "pluginStoppedNotice": "{{name}} is installed but not running. These settings are saved but will not take effect.", + "pluginSettingsSaved": "Settings saved", + "pluginSettingsInvalid": "Some settings could not be saved. Check the highlighted fields.", + "pluginSettingsSaveFailed": "Failed to save settings", + "pluginSettingsLoadFailed": "Failed to load settings", + "pluginsGroupLabel": "Plugins", + "hostPluginNotRunning": "{{name}} is installed but not running. These settings are saved but will not take effect." } } diff --git a/src/ui/settings/HostPluginSections.tsx b/src/ui/settings/HostPluginSections.tsx new file mode 100644 index 000000000..7cd9fb317 --- /dev/null +++ b/src/ui/settings/HostPluginSections.tsx @@ -0,0 +1,140 @@ +/** + * Host editor sections contributed by plugins. + * + * A plugin declares host-scope fields in its manifest and core draws them, so + * a plugin cannot ship its own form styling and a section disappears when its + * plugin does. The enable switch comes first and gates the rest, which is the + * shape the hardcoded feature tabs already have. + * + * Nothing is rendered when no enabled plugin declares host settings, so the + * group does not appear as an empty tab. Phase B fills it as each feature + * moves its columns out of ssh_data. + */ + +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { SectionCard } from "@/components/section-card"; +import { PluginIcon } from "@/lib/plugin-icon"; +import { getPlugins, type PluginSummary } from "@/api/plugins-api"; +import { SettingsFieldRow } from "./SettingsFields"; +import { isFieldActive } from "./settings-fields-util"; + +/** Values for every plugin on one host: { [pluginId]: { [key]: value } }. */ +export type HostPluginSettings = Record>; + +/** Enabled plugins declaring host-scope settings. */ +export function usePluginHostSections(): PluginSummary[] { + const [plugins, setPlugins] = useState([]); + + useEffect(() => { + let cancelled = false; + void getPlugins() + .then((loaded) => { + if (!cancelled) setPlugins(loaded); + }) + .catch(() => { + // Additive: the built-in host tabs do not depend on this. + }); + return () => { + cancelled = true; + }; + }, []); + + return useMemo( + () => + plugins.filter((plugin) => { + if (!plugin.enabled) return false; + const host = plugin.contributes?.settings?.host; + return !!host && (host.fields.length > 0 || !!host.enableKey); + }), + [plugins], + ); +} + +export interface HostPluginSectionsProps { + plugins: PluginSummary[]; + values: HostPluginSettings; + setValue: (pluginId: string, key: string, value: unknown) => void; +} + +export function HostPluginSections({ + plugins, + values, + setValue, +}: HostPluginSectionsProps) { + if (plugins.length === 0) return null; + + return ( +
+ {plugins.map((plugin) => ( + setValue(plugin.id, key, value)} + /> + ))} +
+ ); +} + +function HostPluginSection({ + plugin, + values, + setValue, +}: { + plugin: PluginSummary; + values: Record; + setValue: (key: string, value: unknown) => void; +}) { + const { t } = useTranslation(); + const host = plugin.contributes?.settings?.host; + if (!host) return null; + + const running = plugin.enabled && plugin.state !== "failed"; + const enableKey = host.enableKey; + // With no enable switch the section is always on, which is what a plugin + // declaring only plain fields means. + const enabled = enableKey ? values[enableKey] === true : true; + + return ( + } + > + {!running && ( +
+ {t("settings.hostPluginNotRunning", { name: plugin.name })} +
+ )} + + {enableKey && ( + + )} + + {enabled && + host.fields + .filter((field) => isFieldActive(field, values)) + .map((field) => ( + + ))} +
+ ); +} diff --git a/src/ui/settings/PluginSettingsPage.tsx b/src/ui/settings/PluginSettingsPage.tsx new file mode 100644 index 000000000..65f97537e --- /dev/null +++ b/src/ui/settings/PluginSettingsPage.tsx @@ -0,0 +1,238 @@ +/** + * One plugin's settings page, drawn from its manifest. + * + * The plugin declares groups and fields; Termix renders them with the same + * SectionCard and SettingRow everything else uses. A plugin cannot ship its + * own form styling and drift from the rest of the app, and a page disappears + * cleanly when the plugin is uninstalled. + */ + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Loader2 } from "lucide-react"; +import { toast } from "sonner"; +import { Button } from "@/components/button"; +import { SectionCard } from "@/components/section-card"; +import { pluginKey } from "@/lib/plugin-i18n"; +import { PluginIcon } from "@/lib/plugin-icon"; +import { + getPluginAdminSettings, + getPluginUserSettings, + updatePluginAdminSettings, + updatePluginUserSettings, + PluginSettingsValidationError, + type PluginSettingsErrors, + type PluginSettingsField, + type PluginSummary, +} from "@/api/plugins-api"; +import { SettingsFieldRow } from "./SettingsFields"; +import { isFieldActive } from "./settings-fields-util"; + +interface PluginSettingsPageProps { + plugin: PluginSummary; + isAdmin: boolean; +} + +type Scope = "admin" | "user"; + +export function PluginSettingsPage({ + plugin, + isAdmin, +}: PluginSettingsPageProps) { + const { t } = useTranslation(); + const settings = plugin.contributes?.settings; + const running = plugin.enabled && plugin.state !== "failed"; + + // Admin fields are not merely disabled for a non-admin: the values behind + // them are install-wide configuration they have no business reading. + const scopes = useMemo(() => { + const entries: { scope: Scope; fields: PluginSettingsField[] }[] = []; + if (isAdmin && (settings?.admin?.length ?? 0) > 0) { + entries.push({ scope: "admin", fields: settings!.admin! }); + } + if ((settings?.user?.length ?? 0) > 0) { + entries.push({ scope: "user", fields: settings!.user! }); + } + return entries; + }, [settings, isAdmin]); + + if (scopes.length === 0) { + return ( +
+

+ {t("settings.pluginNoSettings", { name: plugin.name })} +

+
+ ); + } + + return ( +
+ {!running && ( +
+ {t("settings.pluginStoppedNotice", { name: plugin.name })} +
+ )} + + {scopes.map(({ scope, fields }) => ( + + ))} +
+ ); +} + +/** + * One scope's form. + * + * Admin and user settings are separate requests with separate permissions, so + * they save separately rather than sharing one dirty state. + */ +function PluginScopeForm({ + plugin, + scope, + fields, + running, +}: { + plugin: PluginSummary; + scope: Scope; + fields: PluginSettingsField[]; + running: boolean; +}) { + const { t } = useTranslation(); + const [values, setValues] = useState>({}); + const [errors, setErrors] = useState({}); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [dirty, setDirty] = useState(false); + + const load = useCallback(async () => { + setLoading(true); + try { + const loaded = + scope === "admin" + ? await getPluginAdminSettings(plugin.id) + : await getPluginUserSettings(plugin.id); + setValues(loaded); + setErrors({}); + setDirty(false); + } catch { + toast.error(t("settings.pluginSettingsLoadFailed")); + } finally { + setLoading(false); + } + }, [plugin.id, scope, t]); + + useEffect(() => { + void load(); + }, [load]); + + const setValue = (key: string, value: unknown) => { + setValues((current) => ({ ...current, [key]: value })); + setErrors((current) => { + if (!current[key]) return current; + const next = { ...current }; + delete next[key]; + return next; + }); + setDirty(true); + }; + + const save = async () => { + setSaving(true); + try { + const saved = + scope === "admin" + ? await updatePluginAdminSettings(plugin.id, values) + : await updatePluginUserSettings(plugin.id, values); + setValues(saved); + setErrors({}); + setDirty(false); + toast.success(t("settings.pluginSettingsSaved")); + } catch (error) { + if (error instanceof PluginSettingsValidationError) { + setErrors(error.errors); + toast.error(t("settings.pluginSettingsInvalid")); + } else { + toast.error(t("settings.pluginSettingsSaveFailed")); + } + } finally { + setSaving(false); + } + }; + + // Fields keep their declared order within a group, and groups appear in the + // order their first field does, so a manifest controls the whole layout. + const groups = useMemo(() => { + const ordered: { + key: string | undefined; + fields: PluginSettingsField[]; + }[] = []; + for (const field of fields) { + const existing = ordered.find((group) => group.key === field.group); + if (existing) existing.fields.push(field); + else ordered.push({ key: field.group, fields: [field] }); + } + return ordered; + }, [fields]); + + if (loading) { + return ( +
+ + {t("common.loading")} +
+ ); + } + + const icon = ; + + return ( + <> + {groups.map((group, index) => ( + + {group.fields + .filter((field) => isFieldActive(field, values)) + .map((field) => ( + + ))} + + ))} + +
+ +
+ + ); +} diff --git a/src/ui/settings/SettingsFields.tsx b/src/ui/settings/SettingsFields.tsx new file mode 100644 index 000000000..869a86ad0 --- /dev/null +++ b/src/ui/settings/SettingsFields.tsx @@ -0,0 +1,294 @@ +/** + * One control per declared settings field type. + * + * A plugin supplies data; core draws the form. That is what keeps every + * plugin's settings page looking like the rest of the app, and what makes a + * page disappear cleanly when its plugin does. + */ + +import { useTranslation } from "react-i18next"; +import { Input } from "@/components/input"; +import { Textarea } from "@/components/textarea"; +import { Checkbox } from "@/components/checkbox"; +import { PasswordInput } from "@/components/password-input"; +import { Button } from "@/components/button"; +import { FakeSwitch, SettingRow } from "@/components/section-card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/select"; +import { pluginKey } from "@/lib/plugin-i18n"; +import { isRedactedSecret, type PluginSettingsField } from "@/api/plugins-api"; +import { getSettingsComponent } from "./settings-components"; + +export interface SettingsFieldProps { + pluginId: string; + field: PluginSettingsField; + values: Record; + setValue: (key: string, value: unknown) => void; + /** Whether the owning plugin is running. */ + running: boolean; + /** Message from a rejected save, shown under the control. */ + error?: string; + disabled?: boolean; +} + +export function SettingsFieldRow({ + pluginId, + field, + values, + setValue, + running, + error, + disabled = false, +}: SettingsFieldProps) { + const { t } = useTranslation(); + const label = (key?: string) => (key ? t(pluginKey(pluginId, key)) : ""); + + // A custom field draws its own row: it has no single control to put in the + // right-hand slot, and often no label of its own either. + if (field.type === "custom") { + const Component = getSettingsComponent(pluginId, field.component); + if (!Component) return null; + return ( + + ); + } + + const value = values[field.key]; + const readOnly = disabled || !running; + + const description = field.descriptionKey ? ( + <> + {label(field.descriptionKey)} + {error && {error}} + + ) : error ? ( + {error} + ) : undefined; + + return ( + + + + ); +} + +function FieldControl({ + pluginId, + field, + value, + setValue, + readOnly, +}: { + pluginId: string; + field: PluginSettingsField; + value: unknown; + setValue: (key: string, value: unknown) => void; + readOnly: boolean; +}) { + const { t } = useTranslation(); + const label = (key?: string) => (key ? t(pluginKey(pluginId, key)) : ""); + const placeholder = field.placeholderKey + ? label(field.placeholderKey) + : undefined; + + switch (field.type) { + case "boolean": + return ( + setValue(field.key, next)} + /> + ); + + case "number": + return ( + + setValue( + field.key, + event.target.value === "" ? null : Number(event.target.value), + ) + } + /> + ); + + case "select": + return ( + + ); + + case "multiselect": { + const selected = Array.isArray(value) ? (value as string[]) : []; + return ( +
+ {(field.options ?? []).map((option) => ( + + ))} +
+ ); + } + + case "secret": + return ( + + ); + + case "textarea": + case "json": + return ( +