diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index 241930b7d..83a7b5aa5 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -161,7 +161,7 @@ http { root /app/html; index index.html index.htm; expires off; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-IaCkn7qYpeosdQvIY2TXZn11ghTwUsfkSNLXPkjBcSY='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-don5VKNqA8VQplPO1j5zyOVDdbTQrgGSSeVPwJ7b0Lo='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; diff --git a/docker/nginx.conf b/docker/nginx.conf index 31962b585..721d11438 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -142,7 +142,7 @@ http { root /app/html; index index.html index.htm; expires off; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-IaCkn7qYpeosdQvIY2TXZn11ghTwUsfkSNLXPkjBcSY='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-don5VKNqA8VQplPO1j5zyOVDdbTQrgGSSeVPwJ7b0Lo='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; diff --git a/drizzle/mysql/0042_colorful_bill_hollister.sql b/drizzle/mysql/0042_colorful_bill_hollister.sql new file mode 100644 index 000000000..f7269ecd6 --- /dev/null +++ b/drizzle/mysql/0042_colorful_bill_hollister.sql @@ -0,0 +1,7 @@ +-- The remote desktop host options (enable_rdp, enable_vnc, enable_telnet, +-- the three ports, rdp_security, rdp_ignore_cert, security, ignore_cert, +-- guacamole_config) and user_preferences.rdp_defaults moved to the +-- remote-desktop plugin's settings. The copy runs after plugins load, which +-- is after this migration, so a real DROP COLUMN here would delete the data +-- first. The columns stay in place, unused, until 3.0.0. +SELECT 1; diff --git a/drizzle/mysql/meta/0042_snapshot.json b/drizzle/mysql/meta/0042_snapshot.json new file mode 100644 index 000000000..686b5d8c3 --- /dev/null +++ b/drizzle/mysql/meta/0042_snapshot.json @@ -0,0 +1,7597 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "3ab7c6d2-4f87-483a-a0bf-117aa000c3c8", + "prevId": "3bb6c8ff-b151-4722-b1b6-d51946ef5ba5", + "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": {} + }, + "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": {} + }, + "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 + }, + "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 + }, + "ssh_port": { + "name": "ssh_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "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 + }, + "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 + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('sha256')" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_data_id": { + "name": "ssh_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notification_channels_id": { + "name": "notification_channels_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "opkssh_tokens": { + "name": "opkssh_tokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opkssh_tokens_id": { + "name": "opkssh_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('aggregate-telemetry')" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_install_counts_id": { + "name": "plugin_install_counts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_migrations_id": { + "name": "plugin_migrations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('admin')" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_permission_grants_id": { + "name": "plugin_permission_grants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_registries_id": { + "name": "plugin_registries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_settings_id": { + "name": "plugin_settings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_storage_id": { + "name": "plugin_storage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('available')" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('disabled')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugins_id": { + "name": "plugins_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "settings_key": { + "name": "settings_key", + "columns": [ + "key" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('password')" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_credential_secrets_id": { + "name": "shared_credential_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_auth_overrides_id": { + "name": "shared_host_auth_overrides_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('credential')" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_secrets_id": { + "name": "shared_host_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credential_usage_id": { + "name": "ssh_credential_usage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credentials_id": { + "name": "ssh_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_folders_id": { + "name": "ssh_folders_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "sso_providers_id": { + "name": "sso_providers_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_tombstones_id": { + "name": "sync_tombstones_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "termix_identities": { + "name": "termix_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identities_id": { + "name": "termix_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_ca_id": { + "name": "termix_identity_ca_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('manual')" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_keys_id": { + "name": "termix_identity_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "trusted_devices_id": { + "name": "trusted_devices_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ui_preferences_user_id": { + "name": "ui_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_external_identities_id": { + "name": "user_external_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_open_tabs_id": { + "name": "user_open_tabs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_autocomplete": { + "name": "command_autocomplete", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_assistant_enabled": { + "name": "ai_assistant_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_read_only_commands": { + "name": "ai_read_only_commands", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id": { + "name": "user_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_id": { + "name": "user_roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_second_factors_id": { + "name": "user_second_factors_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('openid email profile')" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "vault_profiles": { + "name": "vault_profiles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "vault_profiles_id": { + "name": "vault_profiles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "vault_tokens": { + "name": "vault_tokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_id": { + "name": "profile_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + "user_id", + "profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "vault_tokens_id": { + "name": "vault_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "webauthn_credentials": { + "name": "webauthn_credentials", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('preferred')" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "webauthn_credentials_id": { + "name": "webauthn_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/mysql/meta/_journal.json b/drizzle/mysql/meta/_journal.json index 96608bdae..c9224f663 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -295,6 +295,13 @@ "when": 1790225600497, "tag": "0041_wonderful_cargill", "breakpoints": true + }, + { + "idx": 42, + "version": "5", + "when": 1790227893950, + "tag": "0042_colorful_bill_hollister", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0043_melodic_terror.sql b/drizzle/postgres/0043_melodic_terror.sql new file mode 100644 index 000000000..f7269ecd6 --- /dev/null +++ b/drizzle/postgres/0043_melodic_terror.sql @@ -0,0 +1,7 @@ +-- The remote desktop host options (enable_rdp, enable_vnc, enable_telnet, +-- the three ports, rdp_security, rdp_ignore_cert, security, ignore_cert, +-- guacamole_config) and user_preferences.rdp_defaults moved to the +-- remote-desktop plugin's settings. The copy runs after plugins load, which +-- is after this migration, so a real DROP COLUMN here would delete the data +-- first. The columns stay in place, unused, until 3.0.0. +SELECT 1; diff --git a/drizzle/postgres/meta/0043_snapshot.json b/drizzle/postgres/meta/0043_snapshot.json new file mode 100644 index 000000000..ee18971ef --- /dev/null +++ b/drizzle/postgres/meta/0043_snapshot.json @@ -0,0 +1,7413 @@ +{ + "id": "6a036d52-3dd7-4fe7-aec5-5dbc5244b4f5", + "prevId": "ea943776-71d3-471c-9705-cace583fddef", + "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.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.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 + }, + "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 + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 22 + }, + "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 + }, + "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 + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + { + "expression": "parent_host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_channels": { + "name": "notification_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opkssh_tokens": { + "name": "opkssh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_install_counts": { + "name": "plugin_install_counts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_permission_grants": { + "name": "plugin_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "capability", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_registries": { + "name": "plugin_registries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_settings": { + "name": "plugin_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_credential_secrets": { + "name": "shared_credential_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + { + "expression": "credential_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_secrets": { + "name": "shared_host_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + { + "expression": "host_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credential_usage": { + "name": "ssh_credential_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credentials": { + "name": "ssh_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_folders": { + "name": "ssh_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_providers": { + "name": "sso_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_tombstones": { + "name": "sync_tombstones", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identities": { + "name": "termix_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "nullsNotDistinct": false, + "columns": [ + "handle" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_ca": { + "name": "termix_identity_ca", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "nullsNotDistinct": false, + "columns": [ + "identity_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_keys": { + "name": "termix_identity_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_devices": { + "name": "trusted_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ui_preferences": { + "name": "ui_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_external_identities": { + "name": "user_external_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_open_tabs": { + "name": "user_open_tabs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command_autocomplete": { + "name": "command_autocomplete", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ai_assistant_enabled": { + "name": "ai_assistant_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ai_read_only_commands": { + "name": "ai_read_only_commands", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_second_factors": { + "name": "user_second_factors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "factor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'openid email profile'" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vault_profiles": { + "name": "vault_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vault_tokens": { + "name": "vault_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webauthn_credentials": { + "name": "webauthn_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preferred'" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/postgres/meta/_journal.json b/drizzle/postgres/meta/_journal.json index 764e9e9e4..ae963d2cb 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -302,6 +302,13 @@ "when": 1790225599928, "tag": "0042_strange_shocker", "breakpoints": true + }, + { + "idx": 43, + "version": "7", + "when": 1790227893380, + "tag": "0043_melodic_terror", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0039_lively_impossible_man.sql b/drizzle/sqlite/0039_lively_impossible_man.sql new file mode 100644 index 000000000..f7269ecd6 --- /dev/null +++ b/drizzle/sqlite/0039_lively_impossible_man.sql @@ -0,0 +1,7 @@ +-- The remote desktop host options (enable_rdp, enable_vnc, enable_telnet, +-- the three ports, rdp_security, rdp_ignore_cert, security, ignore_cert, +-- guacamole_config) and user_preferences.rdp_defaults moved to the +-- remote-desktop plugin's settings. The copy runs after plugins load, which +-- is after this migration, so a real DROP COLUMN here would delete the data +-- first. The columns stay in place, unused, until 3.0.0. +SELECT 1; diff --git a/drizzle/sqlite/meta/0039_snapshot.json b/drizzle/sqlite/meta/0039_snapshot.json new file mode 100644 index 000000000..49e89c37b --- /dev/null +++ b/drizzle/sqlite/meta/0039_snapshot.json @@ -0,0 +1,7156 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "0e13985e-2be2-487e-9aaa-510357a7dcb9", + "prevId": "c56e2bf7-9293-4303-9ed5-45608e58e86a", + "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": {} + }, + "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": {} + }, + "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 + }, + "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 + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "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 + }, + "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 + }, + "use_socks5": { + "name": "use_socks5", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "opkssh_tokens": { + "name": "opkssh_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identities": { + "name": "termix_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_autocomplete": { + "name": "command_autocomplete", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_assistant_enabled": { + "name": "ai_assistant_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ai_read_only_commands": { + "name": "ai_read_only_commands", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'openid email profile'" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vault_profiles": { + "name": "vault_profiles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vault_tokens": { + "name": "vault_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + "user_id", + "profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webauthn_credentials": { + "name": "webauthn_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backed_up": { + "name": "backed_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'preferred'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/sqlite/meta/_journal.json b/drizzle/sqlite/meta/_journal.json index d2e698d0b..d0d7d1ab6 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -274,6 +274,13 @@ "when": 1790225599344, "tag": "0038_broad_avengers", "breakpoints": true + }, + { + "idx": 39, + "version": "6", + "when": 1790227892787, + "tag": "0039_lively_impossible_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/electron/main.cjs b/electron/main.cjs index 9ad740e26..02f13c122 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -645,6 +645,7 @@ const isolatedWindows = createIsolatedWindows({ // ctx.desktop.openIsolatedWindow is the one caller today. const BACKEND_REQUEST_HANDLERS = { "open-isolated-window": (payload) => isolatedWindows.open(payload), + "launch-native-rdp": (payload) => launchNativeRdp(payload), }; async function handleBackendRequest(msg) { @@ -1672,10 +1673,6 @@ ipcMain.handle("get-platform", () => { return process.platform; }); -ipcMain.handle("open-native-rdp", (_event, options) => - launchNativeRdp(options), -); - ipcMain.handle("get-embedded-server-status", () => { return { running: diff --git a/electron/preload.js b/electron/preload.js index 14c766654..8e93de8d0 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -30,7 +30,6 @@ contextBridge.exposeInMainWorld("electronAPI", { getPlatform: () => ipcRenderer.invoke("get-platform"), getEmbeddedServerStatus: () => ipcRenderer.invoke("get-embedded-server-status"), - openNativeRdp: (options) => ipcRenderer.invoke("open-native-rdp", options), removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel), isElectron: true, diff --git a/packages/plugin-sdk/ARCHITECTURE.md b/packages/plugin-sdk/ARCHITECTURE.md index 767d13d77..1759a3f39 100644 --- a/packages/plugin-sdk/ARCHITECTURE.md +++ b/packages/plugin-sdk/ARCHITECTURE.md @@ -279,6 +279,12 @@ 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. +`hidden: true` (**B14**) keeps a field out of the generic form: it is stored, +validated and encrypted like any other, but the plugin edits it in its own +UI. Remote desktop's host options are hidden fields, edited in its RDP, VNC +and Telnet host editor tabs, so they do not appear a second time in the +Plugins tab. + `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 @@ -339,8 +345,10 @@ A plugin frontend is a built ESM bundle, `dist/frontend.js`, exporting no imports from `plugins/`; everything a plugin shows goes through the `app` object. `scripts/check-shell-plugin-ids.cjs` (run by lint) fails on a quoted plugin id in `src/ui` outside tests, apart from the entries in -`scripts/shell-plugin-id-allowlist.json`, each with a reason (host protocol -data like `rdp`, which Phase B moves). +`scripts/shell-plugin-id-allowlist.json`, each with a reason (host data like +the `docker` runtime name, which Phase B moves). **B14** removed the `rdp`, +`vnc` and `telnet` entries: the shell learns those protocols from +`app.registerHostProtocol`. #### The loader @@ -427,6 +435,7 @@ boundary and Suspense. | `registerTab` | A tab type, id in `contributes.tabs`. Options cover persistence, layouts, singletons and host needs | | `registerHostEditorSection` | A host editor tab in the top strip or the SSH group, with `form`, `setField` and `updateForm` | | `registerHostAction` | A connect or open action on a host: sidebar row, palette, dashboard, default connect | +| `registerHostProtocol` | A connection protocol next to SSH (**B14**): General tab switch, Hosts panel filter and grouping, Quick Connect, the host's primary type and port | | `registerHostBadge`, `registerHostContextMenuItem` | Host row badges and context menu entries | | `registerPaletteEntry` | A global or per-host command palette entry | | `registerDashboardCard` | A dashboard card, id in `contributes.dashboardCards` | @@ -437,6 +446,7 @@ boundary and Suspense. | `registerSshAuthEditor` | An SSH auth method's editor in the host editor | | `registerLoginMethod`, `registerSecondFactorUI` | Login screen UI for a method and a second factor challenge (plus an optional `enrollment` section) | | `api`, `wsUrl(path)` | axios on `/plugin-api//` and the plugin's WebSocket URL | +| `apiFor(origin)` | The same client for a resolved connection origin: `"remote"` reaches the desktop app's connected server (**B14**) | | `t`, `hasPermission` | The plugin's strings and a permission check, for code outside a component (a toast from `activate`) | | `tabs.open`, `getLayout`, `applyLayout`, `onChange` | Tab control, used by workspaces; `tabs.openRailView` (**B12**) opens a rail view | | `guest`, `info`, `onDispose` | Guest mode flag, plugin info, extra cleanup | @@ -504,6 +514,19 @@ their host info), `terminal.sendToActive(text, { run })` and its terminal tab wrapper populates from the core terminal ref it already holds. The snippets plugin's run/paste flow is the first caller. +**Host protocols (B14).** `app.registerHostProtocol({ id, settingKey, +portKey, defaultPort, titleKey, descriptionKey, icon, quickConnect })` adds a +protocol next to SSH. Its switch and port are the plugin's own host settings, +so core reads them from `host.pluginSettings[]` and never knows the +protocol by name: the General tab draws the switch and saves it into the +plugin's settings, the Hosts panel filters and groups by it, HostItem counts +it, Quick Connect offers it (with a domain field when asked), and a host with +SSH off takes the first protocol switched on as its `connectionType` and its +port. `hostProtocols(host)` from `@termix/plugin-sdk/frontend` lists what a +host has on (`"ssh"` included), for a plugin such as session sharing that +offers every protocol without knowing who owns it. Remote desktop registers +`rdp`, `vnc` and `telnet`. + #### View ownership Who owns a tab, panel or card comes from manifests, so it is known even for a @@ -547,7 +570,11 @@ connection helpers (`isElectron`, `resolveConnectionOrigin`, `pluginWsUrl`, `hydrateLocalSharedHostAuth`, `useConnectionDefaults`); and a few core APIs (`logActivity`, `getHostPassword`, `patchOpenTab`, `getUserPreferences`, `parseCustomKeybindings`, `setHostAutoTmux`, `getCookie`). The APIs are a -stopgap in the right place: D1 turns them into typed bridge members. Publishing its +stopgap in the right place: D1 turns them into typed bridge members. +**B14** added `buildOriginWsUrl`, `getBasePath`, `resolveRemoteHostId` and the +`ConnectionStage` type, for remote desktop's display socket and its remote +host lookup. `useConnectionDefaults` now carries terminal defaults only; the +RDP half became remote desktop's user settings. Publishing its `.d.ts` for plugins outside this repo is a follow-up for the repo split. #### Strings @@ -596,10 +623,12 @@ every enabled plugin that declares `contributes.settings.host`, it looks up `".hostImportNormalizer"` and, if the plugin registered one, calls it with the raw imported row and writes back whatever it returns (or nothing, for `null`). This is not part of the SDK's typed `ctx` surface - it is a plain -`ctx.registry` convention, the same mechanism `remote-desktop.sessions` -already uses, just with a name core's own code knows -to look for. web-endpoint (`src/backend/host-import.ts`) is the first plugin -to register one. +`ctx.registry` convention, just with a name core's own code knows to look +for. web-endpoint (`src/backend/host-import.ts`) is the first plugin to +register one; remote desktop's (**B14**) also reads a Termix export's +`pluginSettings` and the flat fields hosts had before 2.9.0. Host exports +carry every plugin's host settings (secrets redacted) under `pluginSettings`, +which is what makes the round trip work without core naming a plugin. **B9**'s terminal provides `sessions.live` (live SSH sessions: look up, end, remove a share's participants, hand a room share's control over, subscribe to @@ -623,8 +652,11 @@ per-chunk itself), `persist(summary)` and `discard()` for a session that recorded nothing. `createFinished(input)` is the second half of the contract, for a caller that already wrote its own recording to disk and only needs the database row: remote-desktop's guacd recordings use it instead of -importing the old repository directly, and without the plugin enabled a -guacd recording still lands on disk but gets no row. Recording files live +importing the old repository directly. **B14** added `enabledFor(hostId)` +(version 1.1.0), because guacd has to be told to record before the session +exists: remote desktop records an RDP or Telnet session only when the plugin +is running and the host's switch is on, and writes the row as the session's +user. Recording files live under `ctx.files.dataDir()/session_logs//.cast`. Retention (`retentionDays`, an admin setting) runs a sweep at boot and every 24 hours. @@ -647,8 +679,10 @@ any other, `ctx.services.get(service, { provider })` reaches one, and `ctx.services.providers(service)` lists the running ones. A requirement is satisfied by any compatible provider. `sessions.live` is the first: keyed by session type, ssh-terminal provides `ssh` and remote desktop provides `rdp`, -`vnc` and `telnet` (B14), with `createViewerToken(sessionId, readOnly)` for -a viewer. A consumer with no actor, such as a public guest route, calls it +`vnc` and `telnet` (**B14** delivered them), with +`createViewerToken(sessionId, readOnly)` for a viewer. A remote desktop +session id is guacd's own connection id, and `ownerEndSession` closes it and +every viewer joined to it. A consumer with no actor, such as a public guest route, calls it through `ctx.asUser()`. **Session sharing (B12)** is the session-sharing plugin. It provides @@ -954,6 +988,19 @@ dropping a column before its data has moved loses it: 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. +**Order at boot (B14).** On Postgres and MySQL, core's drizzle migrations run +while the database opens, and the copies into `plugin_settings` run later, +after the plugins table exists. A real `DROP COLUMN` in step 4 therefore +deletes the data before it is copied. So step 4 removes the column from +`schema.ts`, the pg/mysql variants and the `db/index.ts` bootstrap, and the +generated drizzle migration's `DROP COLUMN` statements are replaced with a +comment and `SELECT 1;`, exactly as for an adopted table. The column stays in +the database, unused, until 3.0.0 removes it. A copy reads it with raw SQL +through `utils/crypto-migration/raw-rows.ts`, whose `selectRows` and +`runStatement` work on all three engines: `getDb().all()` exists only on +SQLite. Remote desktop's host options and `user_preferences.rdp_defaults` are +the first columns handled this way. + --- ## Manifest v2 reference @@ -1230,6 +1277,8 @@ Built per plugin in `src/backend/plugins/ctx.ts` and passed to `activate`. | `ctx.notify.*` | `notify:send` | A6 | | `ctx.auth.*` | `auth:provide` | **A8** | | `ctx.desktop.openIsolatedWindow` | `desktop:window` | **B8** | +| `ctx.desktop.launchNativeRdp` / `.available` | `desktop:window` (launch only) | **B14** | +| `ctx.credentials.resolveHostProtocol` | `credentials:read` | **B14** | | `ctx.audit.record` | none, the actor is the runtime's | **B9** | | `ctx.fetch` | `network:outbound` | B | @@ -1364,6 +1413,29 @@ share). Rejects outside the desktop app (`ELECTRON_EMBEDDED` unset, or no shares cookies or storage with the main window or with another isolated window. +**B14** added `ctx.desktop.launchNativeRdp({ host, port, username, domain })` +over the same bridge (a second entry in `BACKEND_REQUEST_HANDLERS`, +`launch-native-rdp`), which opens mstsc on Windows with a generated `.rdp` +file that never carries the password, and `ctx.desktop.available()`, which +says whether the server runs embedded in the desktop app. The renderer's own +`openNativeRdp` IPC call is gone; the desktop app's frontend always asks its +embedded backend, which is the one that can reach Electron. + +**B14** also added `ctx.credentials.resolveHostProtocol(hostId, protocol)`, +the first member behind `credentials:read`. It returns the host's address, +jump hosts and the plaintext RDP, VNC or Telnet login the acting user may use, +for a plugin that has to hand them to another program (guacd). The logins +stay in core next to the host: an owner gets the stored login or the stored +credential it points at; a shared recipient gets only core's sharing +resolution (the owner's shared snapshot or their own override), never the +owner's raw secret; no connect access, or no host, is `null`. Every call is +audited as `plugin_credentials_read`, allowed or refused. + +`ctx.hosts.create` (**B14**) hands the created row to every plugin's +`hostImportNormalizer`, the same as a bulk import row, so a plugin creating a +host can set another plugin's host settings (proxmox marks an imported +Windows guest as RDP) without core naming either. + ### The actor Never comes from plugin code. It is held in `AsyncLocalStorage` and set two @@ -1888,8 +1960,8 @@ Then, with the app running: ## Legacy core imports: the debt D1 removes -The bundled plugins predate the SDK, apart from workspaces (A9) and snippets -(B2), which import nothing from core. The others still reach core by relative +The bundled plugins predate the SDK, apart from workspaces (A9), snippets +(B2) and remote-desktop (B14), which import nothing from core. The others still reach core by relative path (`../../../../src/backend/...`), which an esbuild plugin, `packages/plugin-sdk/cli/lib/legacy-core-imports.mjs`, keeps out of the bundle and rewrites to the compiled output path (`../../../backend/backend/...`, or @@ -1916,8 +1988,8 @@ What the lint fence enforces today, in `eslint.config.mjs`: | Core importing a plugin backend | **Error** | 0 | - | | A plugin backend importing frontend code or `@/` | **Error** | 0 | - | | The shell importing plugin code | **Error** | 0 | - | -| A plugin frontend importing core through `@/` | Warning | 130 files | D1 | -| A plugin importing core by relative path | Warning | 66 files | D1 | +| A plugin frontend importing core through `@/` | Warning | 120 files | D1 | +| A plugin importing core by relative path | Warning | 63 files | D1 | | A plugin importing another plugin's source | Warning | 3 files | B18 | A warning does not fail a build, so the counts are held by @@ -1957,6 +2029,13 @@ Known specifics: command history panel into ssh-terminal, which imports nothing from core. The file manager's terminal window renders the `terminal.view` component instead of importing the core terminal. -- Host data columns (`enableDocker`, `enableRdp`, `guacamoleConfig` and so on) - stay in core types until Phase B moves them. Only UI branches on them left - the shell. +- Host data columns (`enableDocker` and so on) stay in core types until + Phase B moves them. Only UI branches on them left the shell. **B14** moved + remote desktop's (`enableRdp`, `enableVnc`, `enableTelnet`, the three ports, + `rdpSecurity`, `rdpIgnoreCert`, `guacamoleConfig`) into its host settings; + the protocol logins stay core host fields. +- **B14** moved remote desktop onto the SDK: it imports nothing from core, + owns no module state, and its guacamole-lite server is built in `activate` + with its own signal handlers removed (guacamole-lite installs SIGTERM and + SIGINT handlers in its constructor). guacd is always an external service, + so there is no `ctx.process`; its address is an admin setting. diff --git a/packages/plugin-sdk/FINISH-LIST.md b/packages/plugin-sdk/FINISH-LIST.md index 6f68865cf..46a47ddc0 100644 --- a/packages/plugin-sdk/FINISH-LIST.md +++ b/packages/plugin-sdk/FINISH-LIST.md @@ -161,11 +161,9 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite ssh-terminal host settings shipped (`ssh-terminal-settings-migration.ts`) and the terminal reads its settings, but core host routes, types, the export/import sample and `HostEditorData` still carry the columns. - `enable_terminal_toolbar` also drives the RDP/VNC/Telnet toolbar - (`remote-desktop/GuacamoleApp.tsx`), whose host editor switch stays in the - General tab, so remote-desktop needs its own host setting and copy first. - Then drop all three in lockstep. Owner: remote-desktop's step for its copy, - then D0. + **B14** gave remote desktop its own `enableToolbar` host setting, copied + from `enable_terminal_toolbar`, so nothing else holds the column up. Drop + all three the B14 way (see "Order at boot" in ARCHITECTURE.md). Owner: D0. - **B9 (ssh-terminal):** the legacy `settings` rows for the terminal (`terminal_session_timeout_minutes`, `terminal_session_persistence_enabled`, `command_history_enabled`, `touch_input_settings`, `terminal_image_*`) are @@ -255,17 +253,6 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite section. Owner: whoever designs the 3.0.0 install flow (prebuild-per-platform packaging, or a documented "avoid native code" rule for community plugins). -- **B12 (session-sharing):** the remote desktop side of sharing has no - provider yet. Session sharing reaches rdp, vnc and telnet sessions through - `sessions.live` named providers `rdp`, `vnc` and `telnet`, each with - `getSession(sessionId)` (id, userId as the session's owner, hostId, - hostName, isConnected, createdAt, tabInstanceId) and - `createViewerToken(sessionId, readOnly)`, typed in - `plugins/session-sharing/src/backend/live.ts`. Until remote-desktop provides - them, sharing or presenting an RDP/VNC/Telnet session is refused and those - links resolve 404. B14 also drops `createJoinToken`/`getSessionInfo` from - its `remote-desktop.sessions` registry entry, which core no longer reads. - Owner: B14. - **B12 (session-sharing):** `allow_session_sharing` is still a live `ssh_data` column. The copy into the plugin's host setting shipped (`session-sharing-settings-migration.ts`) and nothing in core reads or @@ -291,9 +278,8 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite a real behaviour change from the old column default, made once here rather than silently. `ssh-terminal/src/backend/terminal-socket.ts` still reads `resolvedHostData?.enableSessionLogging ?? hostConfig.enableSessionLogging` - off the resolved SSH host instead of `ctx.settings.getHost`, and - `plugins/remote-desktop/src/backend/routes.ts`'s - `host.enableSessionLogging !== false` check, `HostEditorData.ts`, + off the resolved SSH host instead of `ctx.settings.getHost` (remote desktop + asks `recordings.writer.enabledFor` since B14), and `HostEditorData.ts`, `HostManagerData.ts`, `quick-connect-host.ts` and the export/import sample all still read the column directly. The General tab's checkbox was removed from `HostEditor.tsx` (the schema-driven Plugins group replaces it); the @@ -301,14 +287,6 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite (`schema.ts`, `db/index.ts`, a drizzle migration per dialect, `schema:generate`) needs every one of those switched first. Owner: a dedicated follow-up step, or D0. -- **B13 (session-recording):** `remote-desktop`'s guacd recordings now insert - through the optional `recordings.writer` service's `createFinished(input)` - (added alongside `open()`/`RecordingSink` for ssh-terminal's incremental - writer) instead of importing `createCurrentSessionRecordingRepository` - directly. Without the session-recording plugin enabled, a guacd recording - is still written to disk but gets no `session_recordings` row and so never - appears in the list - a warning is logged. Owner: none needed unless a - second consumer of `recordings.writer` appears. - **B13 (session-recording):** `accessId` (the legacy `access_id` column, referencing the now-core-only `host_access` table) is carried in the adopted table's shape as a plain unreferenced integer, since the SDK's @@ -317,6 +295,56 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite in core before this step too). Owner: none needed unless a future step wants to actually use it. +- **B14 (remote-desktop):** the moved columns stay in the database, unused: + `ssh_data.enable_rdp`, `enable_vnc`, `enable_telnet`, `rdp_port`, + `vnc_port`, `telnet_port`, `rdp_security`, `rdp_ignore_cert`, `security`, + `ignore_cert`, `guacamole_config`, and `user_preferences.rdp_defaults`. + Their drizzle migrations are `SELECT 1;` on purpose (the copy runs after + drizzle on Postgres and MySQL). Drop them physically in 3.0.0, once every + install has booted 2.9.0. The legacy `guac_enabled` and `guac_url` rows in + `settings` are left too. Owner: 3.0.0. +- **B14, found in B5 (proxmox):** `drizzle/postgres/0036_redundant_ender_wiggin.sql` + and `drizzle/mysql/0035_clumsy_leech.sql` really `DROP COLUMN` the four + proxmox host columns. On Postgres and MySQL that runs at boot before + `runProxmoxSettingsMigration` copies them, so an upgrade there loses every + host's Proxmox settings. Since nothing has shipped, rewrite those two files + to the `SELECT 1;` pattern (keeping their journal entries). Owner: D0. +- **B14, found in B5 to B13:** every earlier host-column copy + (`proxmox`, `file-manager`, `tunnels`, `web-endpoint`, `ssh-terminal`, + `tmux-monitor`, `session-sharing`, `session-recording` settings migrations) + reads the old columns with `getDb().all(sql...)`, which only exists on + SQLite. On Postgres and MySQL the call throws, the migration's catch logs a + warning and nothing is copied. Switch them to `selectRows` from + `utils/crypto-migration/raw-rows.ts`, which B14 added and uses. Owner: D0. +- **B14, found in B13:** `session-recording-settings-migration.ts` writes + `row.enable_session_logging !== false`, but SQLite returns `0`, so a host + that had recording off migrates as on. Its test only uses real booleans. + Owner: D0. +- **B14 (remote-desktop), for D2:** host payloads carry every plugin's host + settings after the sharing sanitizers, so a connect-level recipient now + receives remote desktop's `guacamoleConfig`, which can hold an RDP gateway + password in plain text. A view-level recipient saw it before B14 too. + Decide whether plugin host settings need a per-field sharing level, or make + the gateway password its own secret field. Owner: D2. +- **B14 (remote-desktop):** core still holds some locale keys that look dead + after this step but may be built dynamically: `hosts.guac.noCredential`, + `authType*`, `recording*`, `saveHostFirst`, `sharingOptionsAfterSave`, + `permissionLevel`, `typeHeader`, `targetHeader`, `permissionHeader`, + `hosts.connectRdp/Vnc/Telnet`, `hosts.copy*UrlAction`, `hosts.telnet` and + `homepage.connType_*`. Owner: whoever next cleans up `src/ui/locales/en.json`, + or D0. +- **Pre-existing, found in B14:** `npm run test -w plugins/web-endpoint` + fails "answers 503 while the tunnels plugin is not available": the route + expects `ctx.services.get` to throw for a missing service, but since B12 + the runtime and the test doubles hand back an empty handle, so the route + calls an undefined `forward` and answers 500. Check `typeof forward` the + way remote desktop does. Owner: D0. +- **Pre-existing, found in B14:** each plugin's own `typecheck` script + (`tsc -p tsconfig.json`) fails with the SDK's `tsconfig.plugin.json` + (node16 resolution wants `.js` on relative imports and cannot resolve + `@termix/plugin-sdk/ui`), workspaces included. `npm run type-check` covers + plugins through `tsconfig.plugins*.json` and passes. Owner: D0. + ## Manual checks after 2.9.0 - SSH terminal: password auth, key auth (and an encrypted key's passphrase @@ -334,3 +362,15 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite off records nothing, retention prunes an old recording on schedule, a guacd RDP/VNC/Telnet recording still gets a row and plays back, and deleting a user anonymizes their recordings instead of removing them. +- Remote desktop: an RDP, a VNC and a Telnet host; a host behind one jump + host (tunnels plugin on and off) and behind a chain of two; macOS Screen + Sharing over VNC with no username; RDP recording (and none with + session-recording off or the host's switch off); file transfer through the + RDP drive, drag and drop included; clipboard both ways; sharing an RDP + session by link (read-only and read-write) and presenting it in a collab + room; opening Windows Remote Desktop from the desktop app on Windows; + changing the guacd URL in admin settings without a restart; turning Remote + Desktop off in admin (actions disappear, connects answer 403); a 2.8 host + that only had `connection_type = rdp` comes up as RDP with SSH off; RDP user + defaults apply to a saved host; Quick Connect over RDP and VNC; the desktop + app with a host on the remote server and one on This device. diff --git a/packages/plugin-sdk/schema/manifest.schema.json b/packages/plugin-sdk/schema/manifest.schema.json index 5df7e9438..ad945290e 100644 --- a/packages/plugin-sdk/schema/manifest.schema.json +++ b/packages/plugin-sdk/schema/manifest.schema.json @@ -510,6 +510,10 @@ "type": "string", "minLength": 1, "description": "Registered component id. Required when type is \"custom\"." + }, + "hidden": { + "type": "boolean", + "description": "Stored and validated, but not drawn by the generic settings form." } } } @@ -602,6 +606,10 @@ "type": "string", "minLength": 1, "description": "Registered component id. Required when type is \"custom\"." + }, + "hidden": { + "type": "boolean", + "description": "Stored and validated, but not drawn by the generic settings form." } } } diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index 8c8509dfb..53afebb95 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -477,7 +477,6 @@ export interface PluginHostRecord { folder: string | null; jumpHosts?: unknown; enableSsh?: boolean | null; - enableRdp?: boolean | null; enableTerminal?: boolean | null; enableFileManager?: boolean | null; enableTunnel?: boolean | null; @@ -487,7 +486,11 @@ export interface PluginHostRecord { [key: string]: unknown; } -/** Fields a plugin may set when creating a host it will own or manage. */ +/** + * Fields a plugin may set when creating a host it will own or manage. Fields + * that belong to another plugin's host settings (enableRdp, rdpPort) are + * handed to that plugin's host import normalizer, as on a bulk import. + */ export type PluginHostCreateInput = Partial< Omit > & { @@ -1116,10 +1119,69 @@ export interface PluginOpenIsolatedWindowRequest { * Electron only: rejects when the server is not running embedded in the * desktop app. Needs desktop:window. */ +export interface PluginNativeRdpRequest { + host: string; + port?: number; + username?: string; + domain?: string; +} + export interface PluginDesktop { openIsolatedWindow: ( request: PluginOpenIsolatedWindowRequest, ) => Promise<{ success: true }>; + /** + * Opens the operating system's own RDP client (mstsc on Windows) for a + * host. The password is never passed; the client asks for it. Windows + * desktop app only. + */ + launchNativeRdp: ( + request: PluginNativeRdpRequest, + ) => Promise<{ success: boolean; error?: string }>; + /** Whether the server runs embedded in the desktop app, so the calls above can work. */ + available: () => boolean; +} + +/** A host protocol whose credentials core keeps next to the host. */ +export type PluginHostProtocol = "rdp" | "vnc" | "telnet"; + +/** + * What a plugin needs to hand a host's protocol login to something core + * does not run (guacd): the address and the plaintext credentials the acting + * user is allowed to use. A shared recipient gets the owner's shared + * snapshot or their own override, never the owner's raw secret. + */ +export interface PluginProtocolTarget { + host: { + id: number; + name: string | null; + ip: string; + /** The host's SSH port, the old fallback when no protocol port is set. */ + port: number; + ownerUserId: string; + jumpHosts: Array<{ hostId: number }>; + }; + /** True when the acting user is not the owner. */ + shared: boolean; + auth: { + /** "direct", "credential", or "none" when the user is asked at connect time. */ + authType: string; + username: string; + password: string; + domain: string; + }; +} + +/** + * Plaintext host credentials, for a plugin that has to hand them to another + * program. Needs credentials:read, and every call is audited. + */ +export interface PluginCredentials { + /** Null when the host does not exist or the acting user cannot connect to it. */ + resolveHostProtocol: ( + hostId: number, + protocol: PluginHostProtocol, + ) => Promise; } /** @@ -1145,7 +1207,7 @@ export interface PluginAudit { * hardware (a serial port, a USB device) is the first caller. Core cannot * mediate that access the way it mediates ctx.ssh or ctx.db, so the * capability here is a declared, reviewable, audited statement of intent - * rather than a technical gate, matching credentials:read and process:spawn. + * rather than a technical gate, matching process:spawn. */ export interface PluginCapabilities { /** Whether this plugin currently holds `capability`. Not audited: for deciding whether to offer something, not for gating an action. */ @@ -1181,6 +1243,8 @@ export interface PluginContext { readonly auth: PluginAuth; /** Opens Electron windows outside the main renderer. Needs desktop:window. */ readonly desktop: PluginDesktop; + /** Plaintext host protocol credentials. Needs credentials:read. */ + readonly credentials: PluginCredentials; /** Audit lines under the plugin's own action names. */ readonly audit: PluginAudit; diff --git a/packages/plugin-sdk/src/frontend.ts b/packages/plugin-sdk/src/frontend.ts index d01692246..2f513dd69 100644 --- a/packages/plugin-sdk/src/frontend.ts +++ b/packages/plugin-sdk/src/frontend.ts @@ -317,6 +317,26 @@ export interface HostActionContribution { }[]; } +/** + * A connection protocol next to SSH, shown as a switch in the host editor's + * General tab and in host filters and Quick Connect. Its switch and port are + * this plugin's own host settings (declare both in contributes.settings.host). + */ +export interface HostProtocolContribution { + id: string; + /** Boolean host setting that turns the protocol on. */ + settingKey: string; + /** Number host setting holding the port. */ + portKey?: string; + defaultPort: number; + titleKey: string; + descriptionKey?: string; + icon: IconComponent; + order?: number; + /** Offer it in Quick Connect, optionally with a domain field. */ + quickConnect?: { showDomain?: boolean }; +} + export interface HostBadgeContribution { id: string; when: (host: PluginHostRecord) => boolean; @@ -524,6 +544,7 @@ export interface TermixApp extends TermixAppInfo { section: HostEditorSectionContribution, ) => Disposer; registerHostAction: (action: HostActionContribution) => Disposer; + registerHostProtocol: (protocol: HostProtocolContribution) => Disposer; registerHostBadge: (badge: HostBadgeContribution) => Disposer; registerHostContextMenuItem: ( item: HostContextMenuItemContribution, @@ -574,6 +595,11 @@ export interface TermixApp extends TermixAppInfo { /** HTTP client for this plugin's /plugin-api// routes. */ api: PluginApiClient; + /** + * The same client for a resolved connection origin: "remote" reaches the + * desktop app's connected remote server, anything else is `api`. + */ + apiFor: (origin?: unknown) => PluginApiClient; /** WebSocket URL and auth subprotocols for /plugin-ws//. */ wsUrl: ( path: string, @@ -668,6 +694,7 @@ export interface PluginHostBridge { usePluginComponent: ( id: string, ) => ComponentType> | undefined; + hostProtocols: (host: PluginHostRecord) => string[]; } let host: PluginHostBridge | null = null; @@ -778,6 +805,15 @@ export function useSshAuthTypes(): { * next to its own. Same permission and `when` filtering as ComponentSlot; * `context` is what each contribution's `when` sees. */ +/** + * The connection protocols a host has switched on: "ssh" plus every protocol + * a running plugin registered with registerHostProtocol (RDP, VNC). Lets a + * plugin offer a protocol without knowing which plugin owns its settings. + */ +export function hostProtocols(record: PluginHostRecord): string[] { + return requireHost().hostProtocols(record); +} + export function useSlotContributions( slotId: string, context?: Record, diff --git a/packages/plugin-sdk/src/manifest.ts b/packages/plugin-sdk/src/manifest.ts index 4e0ab4522..cdc9ffcec 100644 --- a/packages/plugin-sdk/src/manifest.ts +++ b/packages/plugin-sdk/src/manifest.ts @@ -224,6 +224,12 @@ export interface PluginSettingsField { group?: string; /** Registered component id. Required when type is "custom". */ component?: string; + /** + * Stored and validated like any field, but not drawn by the generic + * settings form, because the plugin edits it in its own UI (a host editor + * section). + */ + hidden?: boolean; } export interface PluginHostSettingsContribution { @@ -382,6 +388,7 @@ const ALLOWED_SETTINGS_FIELD = [ "permission", "group", "component", + "hidden", ]; /** Settings keys are stored as-is, so they stay short and index-safe. */ @@ -933,6 +940,9 @@ function validateSettingsFields( if ("permission" in raw) { requireString(raw.permission, `${at}.permission`, errors); } + if ("hidden" in raw && typeof raw.hidden !== "boolean") { + errors.push(`${at}.hidden must be a boolean`); + } if (raw.type === "select" || raw.type === "multiselect") { validateSettingsOptions(raw.options, at, errors); diff --git a/packages/plugin-sdk/src/testing.ts b/packages/plugin-sdk/src/testing.ts index 4667d3247..3e0080cbf 100644 --- a/packages/plugin-sdk/src/testing.ts +++ b/packages/plugin-sdk/src/testing.ts @@ -35,6 +35,10 @@ import type { } from "./backend.js"; import type { SyncEntityRegistration } from "./backend.js"; import type { PluginDatabase } from "./backend.js"; +import type { + PluginNativeRdpRequest, + PluginProtocolTarget, +} from "./backend.js"; export interface FakeContextOptions { pluginId?: string; @@ -69,6 +73,13 @@ export interface FakeContextOptions { shareableRoles?: PluginShareableRole[]; /** Hosts ctx.ssh.resolveHost answers with, secrets included. */ sshHosts?: PluginSshHost[]; + /** + * What ctx.credentials.resolveHostProtocol answers, keyed + * ":". A missing key answers null. + */ + protocolTargets?: Record; + /** What ctx.desktop.available() answers. Defaults to false. */ + desktopAvailable?: boolean; } export interface FakeAuthRegistrations { @@ -123,6 +134,10 @@ export interface FakePluginContext { title?: string; ignoreCert?: boolean; }>; + /** Every ctx.desktop.launchNativeRdp call, in order. */ + nativeRdpLaunches: PluginNativeRdpRequest[]; + /** Every ctx.credentials.resolveHostProtocol call, in order. */ + credentialReads: Array<{ hostId: number; protocol: string }>; /** Every ctx.audit.record entry, in order. */ audits: Array<{ action: string; success: boolean; [key: string]: unknown }>; /** Every ctx.hosts.recordActivity call, in order. */ @@ -206,6 +221,8 @@ export function createFakeContext( const sshConnections: FakePluginContext["sshConnections"] = []; const hostShares: FakePluginContext["hostShares"] = []; const desktopWindows: FakePluginContext["desktopWindows"] = []; + const nativeRdpLaunches: FakePluginContext["nativeRdpLaunches"] = []; + const credentialReads: FakePluginContext["credentialReads"] = []; const audits: FakePluginContext["audits"] = []; const activities: FakePluginContext["activities"] = []; const trackedSessions: number[] = []; @@ -635,6 +652,18 @@ export function createFakeContext( desktopWindows.push(request); return { success: true }; }, + launchNativeRdp: async (request) => { + nativeRdpLaunches.push(request); + return { success: true }; + }, + available: () => options.desktopAvailable ?? false, + }, + + credentials: { + resolveHostProtocol: async (hostId, protocol) => { + credentialReads.push({ hostId, protocol }); + return options.protocolTargets?.[`${hostId}:${protocol}`] ?? null; + }, }, asUser: async (userId, fn) => { @@ -666,6 +695,8 @@ export function createFakeContext( hostShares, auth, desktopWindows, + nativeRdpLaunches, + credentialReads, audits, activities, trackedSessions, @@ -704,6 +735,10 @@ export interface MockContextOptions { services?: Record; /** Hosts ctx.ssh.resolveHost serves. See FakeContextOptions. */ sshHosts?: PluginSshHost[]; + /** What ctx.credentials serves. See FakeContextOptions. */ + protocolTargets?: Record; + /** What ctx.desktop.available() answers. */ + desktopAvailable?: boolean; } export interface MockPluginContext extends FakePluginContext { @@ -740,6 +775,8 @@ export function createMockCtx( hosts: options.hosts, sshHosts: options.sshHosts, services: options.services, + protocolTargets: options.protocolTargets, + desktopAvailable: options.desktopAvailable, manifest: { capabilities: options.capabilities ?? [], ...options.manifest, @@ -972,6 +1009,18 @@ export function createMockCtx( require("desktop:window"); return ctx.desktop.openIsolatedWindow(request); }, + launchNativeRdp: async (request) => { + require("desktop:window"); + return ctx.desktop.launchNativeRdp(request); + }, + available: () => ctx.desktop.available(), + }, + + credentials: { + resolveHostProtocol: async (hostId, protocol) => { + require("credentials:read"); + return ctx.credentials.resolveHostProtocol(hostId, protocol); + }, }, capabilities: { @@ -1180,6 +1229,7 @@ export interface RenderedPluginApp { tabs: () => string[]; panels: () => string[]; hostActions: () => Array<{ id: string; tabType?: string }>; + hostProtocols: () => string[]; hostEditorSections: () => string[]; dashboardCards: () => string[]; settingsComponents: () => string[]; diff --git a/plugins/docker/src/frontend/components/ConsoleTerminal.tsx b/plugins/docker/src/frontend/components/ConsoleTerminal.tsx index 8ff461cf3..bd0112d65 100644 --- a/plugins/docker/src/frontend/components/ConsoleTerminal.tsx +++ b/plugins/docker/src/frontend/components/ConsoleTerminal.tsx @@ -300,7 +300,6 @@ function ConsoleTerminalInner({ const origin = isElectronApp ? await resolveConnectionOrigin({ - connectionType: "ssh", connectionOrigin: hostConfig.connectionOrigin, }) : "local"; diff --git a/plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts b/plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts index 256eb17f8..134c496a9 100644 --- a/plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts +++ b/plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts @@ -982,7 +982,6 @@ export async function ensureSSHSessionForHost( ): Promise { const sessionId = host.id.toString(); const origin = await resolveConnectionOrigin({ - connectionType: host.connectionType, connectionOrigin: host.connectionOrigin, }); setSessionOrigin(sessionId, origin); diff --git a/plugins/proxmox/src/backend/routes.ts b/plugins/proxmox/src/backend/routes.ts index 3c7082ee8..308861d86 100644 --- a/plugins/proxmox/src/backend/routes.ts +++ b/plugins/proxmox/src/backend/routes.ts @@ -652,15 +652,11 @@ async function syncProxmoxHost( rdpUser: null, rdpPassword: null, rdpDomain: null, - rdpSecurity: null, - rdpIgnoreCert: 0, rdpPort: connectionType === "rdp" ? 3389 : null, vncUser: null, vncPassword: null, - vncPort: null, telnetUser: null, telnetPassword: null, - telnetPort: null, defaultPath: "/", tunnelConnections: "[]", jumpHosts: serializeProxmoxJumpHosts(discovery.jumpHosts), diff --git a/plugins/remote-desktop/CHANGELOG.md b/plugins/remote-desktop/CHANGELOG.md index 6a1bc3a37..4c4bf3fab 100644 --- a/plugins/remote-desktop/CHANGELOG.md +++ b/plugins/remote-desktop/CHANGELOG.md @@ -3,3 +3,15 @@ ## 1.0.0 Bundled with Termix 2.9.0. + +- Runs entirely through the plugin SDK: no imports from Termix core. +- Remote Desktop's admin switch and guacd URL, each user's RDP defaults and + every host's RDP, VNC and Telnet options are this plugin's settings now, + moved over on upgrade. +- The user's RDP defaults now apply to saved hosts too, not only Quick Connect. +- Provides `sessions.live` for RDP, VNC and Telnet, so these sessions can be + shared and presented in collab rooms. +- Jump host tunnels and the macOS VNC proxy close with their session instead + of after an hour. +- Opening the Windows Remote Desktop client goes through the desktop app's + backend. diff --git a/plugins/remote-desktop/README.md b/plugins/remote-desktop/README.md index 09d55f7a1..a0bdcc53e 100644 --- a/plugins/remote-desktop/README.md +++ b/plugins/remote-desktop/README.md @@ -1,68 +1,60 @@ # Remote Desktop -RDP, VNC and Telnet remote desktop sessions, packaged as a first-party plugin. +RDP, VNC and Telnet sessions over guacd, with clipboard, file transfer through +RDP drive redirection, session recording and session sharing. -## Why this plugin runs in-process +## guacd -Every other plugin runs in a `worker_threads` worker and reaches the server -through a structured-clone `postMessage` boundary. That boundary is what makes -the capability gate and the audit trail mean anything: a worker plugin cannot -hold a db handle, an ssh2 `Client` or a live socket, so every privileged action -has to go through the broker. +guacd (the Apache Guacamole proxy daemon that speaks RDP, VNC and Telnet) is an +external service this plugin talks to over TCP. The stock Docker compose runs +it as its own `guacamole/guacd` container. This plugin never starts it. -This plugin cannot work that way: +Where to find it is the admin setting **guacd URL** (`host:port`). The +`GUACD_URL`, or `GUACD_HOST` and `GUACD_PORT`, environment variables override +the setting, so a compose file stays the source of truth where it sets them. +Changing the setting rebuilds the connection server without a restart. -- it owns a `guacamole-lite` WebSocket server on port 30008, -- its `/connect-host/:hostId` route does multi-step credential and tunnel - resolution (SSH jump-host tunnels, a macOS VNC compatibility proxy) using - raw `net.Socket` plumbing, -- it needs plaintext host credentials to connect at all. +Other environment variables the plugin reads: -None of that can cross a structured-clone boundary, so this plugin runs on the -main thread instead, under the `process:transport-owner` tier, the same as -`ssh-terminal`, `docker` and `host-metrics`. +| Variable | What it sets | +| ------------------------------ | --------------------------------------------------------------------------- | +| `GUACD_TUNNEL_HOST` | The name guacd uses to reach Termix for a jump host tunnel (default termix) | +| `GUACD_RECORDING_PATH` | Where guacd writes recordings, as guacd sees it | +| `GUACD_RECORDING_BACKEND_PATH` | The same folder as Termix sees it | +| `GUACD_DRIVE_PATH` | The root of each user's RDP drive folder on the guacd host | +| `GUACAMOLE_ENCRYPTION_KEY` | The connection token key; derived from `JWT_SECRET` when unset | -## guacd is not spawned by this plugin +## Settings -guacd (the Apache Guacamole proxy daemon that actually speaks RDP/VNC/Telnet) -is a separate, external service reached over plain TCP -- by default a -sibling Docker container, or any network-reachable host configured through the -admin "guacd URL" setting. This plugin does not download, bundle or spawn -guacd as a child process. +- **Admin**: turn Remote Desktop on or off, and the guacd URL. +- **User**: RDP defaults (colour depth, resize method, wallpaper, clipboard + and the rest). A host's own values win; anything a host leaves on its + default takes the user's. +- **Host**: the RDP, VNC and Telnet switches, ports, security mode, + certificate handling and every guacd parameter, edited in the host + editor's RDP, VNC and Telnet tabs. The toolbar switch is in the Plugins tab. -That is a deliberate limitation, not an oversight: Apache Guacamole does not -publish prebuilt guacd binaries for any platform (only source tarballs and -their own Docker image), and there is no viable native guacd for Windows at -all. Bundling guacd for Linux and macOS is possible in principle but would -make Termix the ongoing upstream builder and security patcher for guacd plus -FreeRDP/libvncclient/libssh2/libtelnet -- out of scope for this conversion. -guacd's availability is handled as an external dependency with graceful -degradation, exactly as it was before this plugin existed. +The logins (users, passwords, stored credentials, domain) stay on the host in +core, because sharing decides which of them a recipient may use. -## What lives where +## Capabilities -Unlike `ssh-terminal` (whose implementation stays in core because -`session-manager.ts` has consumers well beyond the terminal), this plugin's -backend and frontend code is fully relocated here from -`src/backend/hosts/guacamole/` and `src/ui/features/guacamole/`, the same -shape `docker` and `host-metrics` use. +- `credentials:read`: guacd needs the host's RDP, VNC or Telnet password in + plain text, so the plugin reads it through `ctx.credentials`. A shared + recipient only ever gets the owner's shared snapshot or their own override. +- `ssh:connect`, `credentials:use`: jump host tunnels, through + `ctx.ssh.jumpChain`, or the tunnels plugin's `tunnels.access` for a single + hop when it is running. +- `network:serve`: the routes and the `/display` socket. +- `desktop:window`: opening the Windows Remote Desktop client from the desktop + app. +- `hosts:read`: the online indicator while a session is open. +- `ui:surface`: everything the frontend registers. -Two pieces of shared infrastructure still reach across the plugin boundary -into this plugin rather than living in core: +## Services -- `src/backend/hosts/collab/routes.ts` and - `src/backend/hosts/session-sharing/` import `token-service.ts` and - `guacamole-server.ts` directly to resolve and validate live Guacamole - sessions for collaborative/shared viewing. -- `src/ui/features/collab/` and `src/ui/features/session-sharing/` import - `GuacamoleDisplay.tsx` directly for the same reason. - -This is an accepted, temporary coupling: collab and session-sharing are -expected to become plugins of their own later, at which point these become -real inter-plugin references instead of raw relative imports. - -## Disabling - -Disabling behaves like any other plugin: the rdp/vnc/telnet tabs stop being -offered from the host list, command palette and quick connect, the -`/guacamole` router 404s, and the WebSocket server on port 30008 is closed. +Provides `sessions.live` under the names `rdp`, `vnc` and `telnet`, which is +how session sharing and collab rooms find a live session and mint a viewer +token for it. Uses `recordings.writer` (session-recording) to decide whether +to record and to list finished recordings, and `tunnels.access` (tunnels) +for single-hop jump tunnels. Both are optional. diff --git a/plugins/remote-desktop/locales/en.json b/plugins/remote-desktop/locales/en.json index 7caeafab0..0af8c9d5f 100644 --- a/plugins/remote-desktop/locales/en.json +++ b/plugins/remote-desktop/locales/en.json @@ -73,7 +73,8 @@ "fitToScreen": "Fit to screen", "switchToTrackpad": "Switch to trackpad mode (drag to move cursor, tap to click)", "switchToTouch": "Switch to touch mode (tap directly where you want to click)" - } + }, + "disabled": "Remote Desktop is turned off. An admin can turn it on in Settings." }, "hosts": { "nativeRdpOpened": "Opened in Windows Remote Desktop", @@ -204,11 +205,134 @@ "fontName": "Font Name", "fontSize": "Font Size", "colorScheme": "Color Scheme", - "backspaceKey": "Backspace Key" - } + "backspaceKey": "Backspace Key", + "colorDepth": "Color Depth", + "resizeMethod": "Resize Method" + }, + "remoteDesktop": "Remote Desktop", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtual Network", + "unencryptedShell": "Unencrypted shell", + "remoteDesktopNotes": "Notes", + "userDefaultsNote": "Settings left on their default use your Remote Desktop defaults from Settings.", + "connectionOriginNote": "On the desktop app, RDP, VNC and Telnet use the remote server unless the host is set to This device, which needs a guacd this machine can reach. Set one in the Remote Desktop settings, or per host under guacd Proxy." }, "onboarding": { "feature_desktop": "Remote desktop", "feature_desktop_desc": "Open RDP, VNC and Telnet sessions in a tab, same as SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Color Depth", + "description": "Used for RDP hosts that leave it on their default." + }, + "resizeMethod": { + "label": "Resize Method", + "description": "How the remote screen follows the window size." + }, + "resizeDisplayUpdate": "Display Update", + "resizeReconnect": "Reconnect", + "inherit": "Inherit", + "on": "On", + "off": "Off", + "forceLossless": { + "label": "Force Lossless", + "description": "Force lossless image encoding (higher quality, more bandwidth)" + }, + "enableWallpaper": { + "label": "Wallpaper", + "description": "Show desktop wallpaper (disabling improves performance)" + }, + "enableFontSmoothing": { + "label": "Font Smoothing", + "description": "Enable ClearType font rendering" + }, + "enableDesktopComposition": { + "label": "Desktop Composition", + "description": "Enable Aero glass effects" + }, + "disableAudio": { + "label": "Disable Audio", + "description": "Mute all audio from the remote session" + }, + "enablePrinting": { + "label": "Enable Printing", + "description": "Redirect local printers to the remote session" + }, + "enableDrive": { + "label": "Enable Drive Redirection", + "description": "Map a local folder as a drive in the remote session" + }, + "disableCopy": { + "label": "Disable Copy", + "description": "Prevent copying text from the remote session" + }, + "disablePaste": { + "label": "Disable Paste", + "description": "Prevent pasting text into the remote session" + }, + "group": "Remote Desktop defaults", + "colorDepth16": "16-bit", + "colorDepth24": "24-bit", + "colorDepth32": "32-bit" + }, + "admin": { + "enabled": { + "label": "Enable Remote Desktop", + "description": "Allow RDP, VNC and Telnet connections through guacd." + }, + "guacdUrl": { + "label": "guacd URL", + "description": "Where guacd listens, as host:port. The GUACD_URL, GUACD_HOST and GUACD_PORT environment variables override this.", + "placeholder": "guacd:4822" + } + }, + "host": { + "enableRdp": { + "label": "RDP" + }, + "enableVnc": { + "label": "VNC" + }, + "enableTelnet": { + "label": "Telnet" + }, + "rdpPort": { + "label": "RDP port" + }, + "vncPort": { + "label": "VNC port" + }, + "telnetPort": { + "label": "Telnet port" + }, + "rdpSecurity": { + "label": "RDP security mode" + }, + "rdpIgnoreCert": { + "label": "Ignore certificate errors" + }, + "guacamoleConfig": { + "label": "guacd settings" + }, + "enableToolbar": { + "label": "Remote desktop toolbar", + "description": "Show the toolbar in RDP, VNC and Telnet sessions." + } + } + }, + "palette": { + "connectRdp": "Connect with RDP", + "connectVnc": "Connect with VNC", + "connectTelnet": "Connect with Telnet" + }, + "permissions": { + "sessions": { + "title": "Share remote desktop sessions", + "description": "Let session sharing and collab rooms show this user's RDP, VNC and Telnet sessions to others." + } } } diff --git a/plugins/remote-desktop/locales/translated/af_ZA.json b/plugins/remote-desktop/locales/translated/af_ZA.json index b3229544d..30b2f3296 100644 --- a/plugins/remote-desktop/locales/translated/af_ZA.json +++ b/plugins/remote-desktop/locales/translated/af_ZA.json @@ -204,11 +204,75 @@ "fontName": "Lettertipe Naam", "fontSize": "Lettergrootte", "colorScheme": "Kleurskema", - "backspaceKey": "Terugspasie-sleutel" - } + "backspaceKey": "Terugspasie-sleutel", + "colorDepth": "Kleurdiepte", + "resizeMethod": "Vergrootingsmetode" + }, + "remoteDesktop": "Afstandslessenaar", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuele Netwerk", + "unencryptedShell": "Ongeënkripteerde dop" }, "onboarding": { "feature_desktop": "Afstandlessenaar", "feature_desktop_desc": "Maak RDP-, VNC- en Telnet-sessies in 'n oortjie oop, dieselfde as SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Kleurdiepte" + }, + "resizeMethod": { + "label": "Vergrootingsmetode" + }, + "resizeDisplayUpdate": "Skermopdatering", + "resizeReconnect": "Herkoppel", + "inherit": "Erf", + "on": "Aan", + "off": "Af", + "forceLossless": { + "label": "Forseer verliesloos", + "description": "Forseer verlieslose beeldkodering (hoër gehalte, meer bandwydte)" + }, + "enableWallpaper": { + "label": "Agtergrond", + "description": "Wys lessenaar agtergrond (deaktivering verbeter werkverrigting)" + }, + "enableFontSmoothing": { + "label": "Lettertipe-gladmaak", + "description": "Aktiveer ClearType-lettertipe-weergawe" + }, + "enableDesktopComposition": { + "label": "Komposisie op die lessenaar", + "description": "Aktiveer Aero-glaseffekte" + }, + "disableAudio": { + "label": "Deaktiveer oudio", + "description": "Demp alle klank van die afstandsessie" + }, + "enablePrinting": { + "label": "Aktiveer drukwerk", + "description": "Herlei plaaslike drukkers na die afgeleë sessie" + }, + "enableDrive": { + "label": "Aktiveer dryfherleiding", + "description": "Karaktereer 'n plaaslike vouer as 'n skyf in die afstandsessie" + }, + "disableCopy": { + "label": "Deaktiveer Kopieer", + "description": "Voorkom die kopiëring van teks vanaf die afgeleë sessie" + }, + "disablePaste": { + "label": "Deaktiveer plak", + "description": "Voorkom die plak van teks in die afgeleë sessie" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd-URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/ar_SA.json b/plugins/remote-desktop/locales/translated/ar_SA.json index 2365cd9c8..158c02d59 100644 --- a/plugins/remote-desktop/locales/translated/ar_SA.json +++ b/plugins/remote-desktop/locales/translated/ar_SA.json @@ -204,11 +204,75 @@ "fontName": "اسم الخط", "fontSize": "حجم الخط", "colorScheme": "نظام الألوان", - "backspaceKey": "مفتاح Backspace" - } + "backspaceKey": "مفتاح Backspace", + "colorDepth": "عمق اللون", + "resizeMethod": "طريقة تغيير الحجم" + }, + "remoteDesktop": "سطح المكتب البعيد", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "الشبكة الافتراضية", + "unencryptedShell": "shell غير مشفر" }, "onboarding": { "feature_desktop": "سطح المكتب البعيد", "feature_desktop_desc": "افتح جلسات RDP وVNC وTelnet في علامة تبويب، تمامًا مثل SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "عمق اللون" + }, + "resizeMethod": { + "label": "طريقة تغيير الحجم" + }, + "resizeDisplayUpdate": "تحديث الشاشة", + "resizeReconnect": "إعادة الاتصال", + "inherit": "يرث", + "on": "على", + "off": "عن", + "forceLossless": { + "label": "فرض بدون فقدان", + "description": "فرض ترميز الصور بدون فقدان (جودة أعلى، استهلاك أكبر للنطاق الترددي)" + }, + "enableWallpaper": { + "label": "خلفية سطح المكتب", + "description": "إظهار خلفية سطح المكتب (التعطيل يحسن الأداء)" + }, + "enableFontSmoothing": { + "label": "تنعيم الخطوط", + "description": "تمكين عرض خطوط ClearType" + }, + "enableDesktopComposition": { + "label": "تركيب سطح المكتب", + "description": "تمكين تأثيرات Aero الزجاجية" + }, + "disableAudio": { + "label": "تعطيل الصوت", + "description": "كتم كل الصوت من الجلسة البعيدة" + }, + "enablePrinting": { + "label": "تمكين الطباعة", + "description": "إعادة توجيه الطابعات المحلية إلى الجلسة البعيدة" + }, + "enableDrive": { + "label": "تمكين إعادة توجيه القرص", + "description": "تعيين مجلد محلي كقرص في الجلسة البعيدة" + }, + "disableCopy": { + "label": "تعطيل النسخ", + "description": "منع نسخ النص من الجلسة البعيدة" + }, + "disablePaste": { + "label": "تعطيل اللصق", + "description": "منع لصق النص في الجلسة البعيدة" + } + }, + "admin": { + "guacdUrl": { + "label": "رابط guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/bg_BG.json b/plugins/remote-desktop/locales/translated/bg_BG.json index f8d35c6d4..1e69af9d3 100644 --- a/plugins/remote-desktop/locales/translated/bg_BG.json +++ b/plugins/remote-desktop/locales/translated/bg_BG.json @@ -204,11 +204,75 @@ "fontName": "Име на шрифта", "fontSize": "Размер на шрифта", "colorScheme": "Цветова схема", - "backspaceKey": "Клавиш Backspace" - } + "backspaceKey": "Клавиш Backspace", + "colorDepth": "Дълбочина на цвета", + "resizeMethod": "Метод за преоразмеряване" + }, + "remoteDesktop": "Отдалечен работен плот", + "tabRdp": "ПРСР", + "tabVnc": "VNC", + "tabTelnet": "Телнет", + "virtualNetwork": "Виртуална мрежа", + "unencryptedShell": "Некриптирана обвивка" }, "onboarding": { "feature_desktop": "Отдалечен работен плот", "feature_desktop_desc": "Отваряне на RDP, VNC и Telnet сесии в раздел, както при SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Дълбочина на цвета" + }, + "resizeMethod": { + "label": "Метод за преоразмеряване" + }, + "resizeDisplayUpdate": "Актуализация на дисплея", + "resizeReconnect": "Свържете се отново", + "inherit": "Наследяване", + "on": "Включено", + "off": "Изключено", + "forceLossless": { + "label": "Принудително беззагубно", + "description": "Принудително кодиране на изображения без загуби (по-високо качество, по-голяма честотна лента)" + }, + "enableWallpaper": { + "label": "Тапет", + "description": "Показване на тапет на работния плот (деактивирането му подобрява производителността)" + }, + "enableFontSmoothing": { + "label": "Изглаждане на шрифта", + "description": "Активиране на рендирането на шрифтове ClearType" + }, + "enableDesktopComposition": { + "label": "Композиция на работния плот", + "description": "Активиране на ефектите на Aero Glass" + }, + "disableAudio": { + "label": "Деактивиране на звука", + "description": "Изключване на звука от отдалечената сесия" + }, + "enablePrinting": { + "label": "Активиране на печат", + "description": "Пренасочване на локални принтери към отдалечена сесия" + }, + "enableDrive": { + "label": "Активиране на пренасочване на устройството", + "description": "Карта на локална папка като устройство в отдалечена сесия" + }, + "disableCopy": { + "label": "Деактивиране на копирането", + "description": "Предотвратяване на копирането на текст от отдалечената сесия" + }, + "disablePaste": { + "label": "Деактивиране на поставянето", + "description": "Предотвратяване на поставянето на текст в отдалечената сесия" + } + }, + "admin": { + "guacdUrl": { + "label": "URL адрес на GUACD" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/bn_BD.json b/plugins/remote-desktop/locales/translated/bn_BD.json index def002c2d..0de4a78c3 100644 --- a/plugins/remote-desktop/locales/translated/bn_BD.json +++ b/plugins/remote-desktop/locales/translated/bn_BD.json @@ -204,11 +204,75 @@ "fontName": "ফন্টের নাম", "fontSize": "ফন্ট সাইজ", "colorScheme": "রঙের পরিকল্পনা", - "backspaceKey": "ব্যাকস্পেস কী" - } + "backspaceKey": "ব্যাকস্পেস কী", + "colorDepth": "রঙের গভীরতা", + "resizeMethod": "আকার পরিবর্তন পদ্ধতি" + }, + "remoteDesktop": "রিমোট ডেস্কটপ", + "tabRdp": "আরডিপি", + "tabVnc": "ভিএনসি", + "tabTelnet": "টেলনেট", + "virtualNetwork": "ভার্চুয়াল নেটওয়ার্ক", + "unencryptedShell": "এনক্রিপ্টবিহীন শেল" }, "onboarding": { "feature_desktop": "রিমোট ডেস্কটপ", "feature_desktop_desc": "SSH-এর মতোই একটি ট্যাবে RDP, VNC এবং Telnet সেশন খুলুন।" + }, + "settings": { + "user": { + "colorDepth": { + "label": "রঙের গভীরতা" + }, + "resizeMethod": { + "label": "আকার পরিবর্তন পদ্ধতি" + }, + "resizeDisplayUpdate": "ডিসপ্লে আপডেট", + "resizeReconnect": "পুনরায় সংযোগ করুন", + "inherit": "উত্তরাধিকার সূত্রে", + "on": "চালু", + "off": "বন্ধ", + "forceLossless": { + "label": "বল ক্ষয়হীন", + "description": "লসলেস ইমেজ এনকোডিং বাধ্যতামূলক করুন (উচ্চতর গুণমান, বেশি ব্যান্ডউইথ)" + }, + "enableWallpaper": { + "label": "ওয়ালপেপার", + "description": "ডেস্কটপ ওয়ালপেপার দেখান (এটি বন্ধ করলে পারফরম্যান্স উন্নত হয়)" + }, + "enableFontSmoothing": { + "label": "ফন্ট মসৃণকরণ", + "description": "ক্লিয়ারটাইপ ফন্ট রেন্ডারিং সক্ষম করুন" + }, + "enableDesktopComposition": { + "label": "ডেস্কটপ কম্পোজিশন", + "description": "এরো গ্লাস এফেক্টস সক্রিয় করুন" + }, + "disableAudio": { + "label": "অডিও নিষ্ক্রিয় করুন", + "description": "রিমোট সেশন থেকে সমস্ত অডিও মিউট করুন" + }, + "enablePrinting": { + "label": "প্রিন্টিং সক্ষম করুন", + "description": "স্থানীয় প্রিন্টারগুলিকে রিমোট সেশনে পুনঃনির্দেশিত করুন" + }, + "enableDrive": { + "label": "ড্রাইভ পুনঃনির্দেশনা সক্ষম করুন", + "description": "রিমোট সেশনে একটি স্থানীয় ফোল্ডারকে ড্রাইভ হিসেবে ম্যাপ করুন।" + }, + "disableCopy": { + "label": "কপি নিষ্ক্রিয় করুন", + "description": "রিমোট সেশন থেকে টেক্সট কপি করা প্রতিরোধ করুন" + }, + "disablePaste": { + "label": "পেস্ট নিষ্ক্রিয় করুন", + "description": "রিমোট সেশনে টেক্সট পেস্ট করা প্রতিরোধ করুন" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/ca_ES.json b/plugins/remote-desktop/locales/translated/ca_ES.json index 24e412192..ebb07cd61 100644 --- a/plugins/remote-desktop/locales/translated/ca_ES.json +++ b/plugins/remote-desktop/locales/translated/ca_ES.json @@ -204,11 +204,75 @@ "fontName": "Nom de la font", "fontSize": "Mida de la lletra", "colorScheme": "Esquema de colors", - "backspaceKey": "Tecla Retrocés" - } + "backspaceKey": "Tecla Retrocés", + "colorDepth": "Profunditat de color", + "resizeMethod": "Mètode de redimensionament" + }, + "remoteDesktop": "Escriptori remot", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Xarxa virtual", + "unencryptedShell": "Shell sense xifrar" }, "onboarding": { "feature_desktop": "Escriptori remot", "feature_desktop_desc": "Obriu sessions RDP, VNC i Telnet en una pestanya, igual que SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Profunditat de color" + }, + "resizeMethod": { + "label": "Mètode de redimensionament" + }, + "resizeDisplayUpdate": "Actualització de la pantalla", + "resizeReconnect": "Reconnecta", + "inherit": "Heretar", + "on": "Activat", + "off": "Desactivat", + "forceLossless": { + "label": "Força sense pèrdues", + "description": "Força la codificació d'imatges sense pèrdues (més qualitat, més amplada de banda)" + }, + "enableWallpaper": { + "label": "Fons de pantalla", + "description": "Mostra el fons de pantalla de l'escriptori (desactivar-lo millora el rendiment)" + }, + "enableFontSmoothing": { + "label": "Suavitzat de fonts", + "description": "Activa la renderització de fonts ClearType" + }, + "enableDesktopComposition": { + "label": "Composició d'escriptori", + "description": "Activa els efectes Aero Glass" + }, + "disableAudio": { + "label": "Desactiva l'àudio", + "description": "Silencia tot l'àudio de la sessió remota" + }, + "enablePrinting": { + "label": "Habilita la impressió", + "description": "Redirigir les impressores locals a la sessió remota" + }, + "enableDrive": { + "label": "Activa la redirecció de la unitat", + "description": "Assignar una carpeta local com a unitat a la sessió remota" + }, + "disableCopy": { + "label": "Desactiva la còpia", + "description": "Impedir la còpia de text des de la sessió remota" + }, + "disablePaste": { + "label": "Desactiva l'enganxament", + "description": "Impedir enganxar text a la sessió remota" + } + }, + "admin": { + "guacdUrl": { + "label": "URL de guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/cs_CZ.json b/plugins/remote-desktop/locales/translated/cs_CZ.json index d6038ddad..f366bda87 100644 --- a/plugins/remote-desktop/locales/translated/cs_CZ.json +++ b/plugins/remote-desktop/locales/translated/cs_CZ.json @@ -204,11 +204,75 @@ "fontName": "Název písma", "fontSize": "Velikost písma", "colorScheme": "Barevné schéma", - "backspaceKey": "Klávesa Backspace" - } + "backspaceKey": "Klávesa Backspace", + "colorDepth": "Barevná hloubka", + "resizeMethod": "Metoda změny velikosti" + }, + "remoteDesktop": "Vzdálená plocha", + "tabRdp": "PRV", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuální síť", + "unencryptedShell": "Nešifrovaný shell" }, "onboarding": { "feature_desktop": "Vzdálená plocha", "feature_desktop_desc": "Otevírejte relace RDP, VNC a Telnet na kartě, stejně jako SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Barevná hloubka" + }, + "resizeMethod": { + "label": "Metoda změny velikosti" + }, + "resizeDisplayUpdate": "Aktualizace displeje", + "resizeReconnect": "Znovu se připojte", + "inherit": "Zdědit", + "on": "Na", + "off": "Vypnuto", + "forceLossless": { + "label": "Vynutit bezztrátové", + "description": "Vynucení bezztrátového kódování obrazu (vyšší kvalita, větší šířka pásma)" + }, + "enableWallpaper": { + "label": "Tapeta", + "description": "Zobrazit tapetu plochy (zakázání zlepšuje výkon)" + }, + "enableFontSmoothing": { + "label": "Vyhlazování písma", + "description": "Povolit vykreslování písma ClearType" + }, + "enableDesktopComposition": { + "label": "Složení plochy", + "description": "Povolit efekty Aero Glass" + }, + "disableAudio": { + "label": "Zakázat zvuk", + "description": "Ztlumit veškerý zvuk ze vzdálené relace" + }, + "enablePrinting": { + "label": "Povolit tisk", + "description": "Přesměrování lokálních tiskáren na vzdálenou relaci" + }, + "enableDrive": { + "label": "Povolit přesměrování disku", + "description": "Namapování lokální složky jako jednotky ve vzdálené relaci" + }, + "disableCopy": { + "label": "Zakázat kopírování", + "description": "Zabránit kopírování textu ze vzdálené relace" + }, + "disablePaste": { + "label": "Zakázat vkládání", + "description": "Zabránit vkládání textu do vzdálené relace" + } + }, + "admin": { + "guacdUrl": { + "label": "URL GUACD" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/da_DK.json b/plugins/remote-desktop/locales/translated/da_DK.json index 571123d1b..8c70099bc 100644 --- a/plugins/remote-desktop/locales/translated/da_DK.json +++ b/plugins/remote-desktop/locales/translated/da_DK.json @@ -204,11 +204,75 @@ "fontName": "Skrifttypenavn", "fontSize": "Skriftstørrelse", "colorScheme": "Farveskema", - "backspaceKey": "Tilbagetast" - } + "backspaceKey": "Tilbagetast", + "colorDepth": "Farvedybde", + "resizeMethod": "Ændre størrelse-metode" + }, + "remoteDesktop": "Fjernskrivebord", + "tabRdp": "Landdistriktsudviklingsplan", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuelt netværk", + "unencryptedShell": "Ukrypteret skal" }, "onboarding": { "feature_desktop": "Fjernskrivebord", "feature_desktop_desc": "Åbn RDP-, VNC- og Telnet-sessioner i en fane, ligesom med SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Farvedybde" + }, + "resizeMethod": { + "label": "Ændre størrelse-metode" + }, + "resizeDisplayUpdate": "Opdatering af display", + "resizeReconnect": "Genopret forbindelse", + "inherit": "Arve", + "on": "På", + "off": "Slukket", + "forceLossless": { + "label": "Tving tabsfri", + "description": "Tving tabsfri billedkodning (højere kvalitet, mere båndbredde)" + }, + "enableWallpaper": { + "label": "Tapet", + "description": "Vis skrivebordsbaggrund (deaktivering forbedrer ydeevnen)" + }, + "enableFontSmoothing": { + "label": "Skrifttypeudjævning", + "description": "Aktivér gengivelse af ClearType-skrifttyper" + }, + "enableDesktopComposition": { + "label": "Skrivebordskomposition", + "description": "Aktivér Aero Glass-effekter" + }, + "disableAudio": { + "label": "Deaktiver lyd", + "description": "Slå al lyd fra fra fjernsessionen" + }, + "enablePrinting": { + "label": "Aktivér udskrivning", + "description": "Omdiriger lokale printere til fjernsessionen" + }, + "enableDrive": { + "label": "Aktivér drevomdirigering", + "description": "Tilknyt en lokal mappe som et drev i den eksterne session" + }, + "disableCopy": { + "label": "Deaktiver kopiering", + "description": "Forhindr kopiering af tekst fra fjernsessionen" + }, + "disablePaste": { + "label": "Deaktiver indsætning", + "description": "Forhindr indsættelse af tekst i fjernsessionen" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd-URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/de_DE.json b/plugins/remote-desktop/locales/translated/de_DE.json index f9cd43e46..c2793a1d4 100644 --- a/plugins/remote-desktop/locales/translated/de_DE.json +++ b/plugins/remote-desktop/locales/translated/de_DE.json @@ -204,11 +204,75 @@ "fontName": "Schriftname", "fontSize": "Schriftgröße", "colorScheme": "Farbschema", - "backspaceKey": "Backspace-Taste" - } + "backspaceKey": "Backspace-Taste", + "colorDepth": "Farbtiefe", + "resizeMethod": "Skalierungsmethode" + }, + "remoteDesktop": "Remotedesktop", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuelles Netzwerk", + "unencryptedShell": "Unverschlüsselte Shell" }, "onboarding": { "feature_desktop": "Remote-Desktop", "feature_desktop_desc": "Öffnen Sie RDP-, VNC- und Telnet-Sitzungen in einem Tab, genau wie bei SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Farbtiefe" + }, + "resizeMethod": { + "label": "Skalierungsmethode" + }, + "resizeDisplayUpdate": "Anzeigeaktualisierung", + "resizeReconnect": "Wiederverbinden", + "inherit": "Erben", + "on": "An", + "off": "Aus", + "forceLossless": { + "label": "Verlustfreie Übertragung erzwingen", + "description": "Verlustfreie Bildcodierung erzwingen (höhere Qualität, mehr Bandbreite)" + }, + "enableWallpaper": { + "label": "Hintergrundbild", + "description": "Desktop-Hintergrundbild anzeigen (Deaktivierung verbessert die Leistung)" + }, + "enableFontSmoothing": { + "label": "Schriftglättung", + "description": "ClearType-Schriftdarstellung aktivieren" + }, + "enableDesktopComposition": { + "label": "Desktopkomposition", + "description": "Aero-Glaseffekte aktivieren" + }, + "disableAudio": { + "label": "Audio deaktivieren", + "description": "Gesamten Ton der Remote-Sitzung stummschalten" + }, + "enablePrinting": { + "label": "Drucken aktivieren", + "description": "Lokale Drucker an die Remote-Sitzung umleiten" + }, + "enableDrive": { + "label": "Laufwerksumleitung aktivieren", + "description": "Lokalen Ordner als Laufwerk in der Remote-Sitzung einbinden" + }, + "disableCopy": { + "label": "Kopieren deaktivieren", + "description": "Kopieren von Text aus der entfernten Sitzung verhindern" + }, + "disablePaste": { + "label": "Einfügen deaktivieren", + "description": "Einfügen von Text in die entfernte Sitzung verhindern" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd-URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/el_GR.json b/plugins/remote-desktop/locales/translated/el_GR.json index 7d07e4a81..17b1e8720 100644 --- a/plugins/remote-desktop/locales/translated/el_GR.json +++ b/plugins/remote-desktop/locales/translated/el_GR.json @@ -204,11 +204,75 @@ "fontName": "Όνομα γραμματοσειράς", "fontSize": "Μέγεθος γραμματοσειράς", "colorScheme": "Χρωματικό σχήμα", - "backspaceKey": "Πλήκτρο Backspace" - } + "backspaceKey": "Πλήκτρο Backspace", + "colorDepth": "Βάθος χρώματος", + "resizeMethod": "Μέθοδος αλλαγής μεγέθους" + }, + "remoteDesktop": "Απομακρυσμένη επιφάνεια εργασίας", + "tabRdp": "ΠΑΑ", + "tabVnc": "VNC", + "tabTelnet": "Τέλνετ", + "virtualNetwork": "Εικονικό Δίκτυο", + "unencryptedShell": "Μη κρυπτογραφημένο κέλυφος" }, "onboarding": { "feature_desktop": "Απομακρυσμένη επιφάνεια εργασίας", "feature_desktop_desc": "Ανοίξτε τις συνεδρίες RDP, VNC και Telnet σε μια καρτέλα, όπως και το SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Βάθος χρώματος" + }, + "resizeMethod": { + "label": "Μέθοδος αλλαγής μεγέθους" + }, + "resizeDisplayUpdate": "Ενημέρωση οθόνης", + "resizeReconnect": "Επανασύνδεση", + "inherit": "Κληρονομώ", + "on": "Επί", + "off": "Μακριά από", + "forceLossless": { + "label": "Δύναμη χωρίς απώλειες", + "description": "Επιβολή κωδικοποίησης εικόνας χωρίς απώλειες (υψηλότερη ποιότητα, μεγαλύτερο εύρος ζώνης)" + }, + "enableWallpaper": { + "label": "Ταπετσαρία", + "description": "Εμφάνιση ταπετσαρίας επιφάνειας εργασίας (η απενεργοποίηση βελτιώνει την απόδοση)" + }, + "enableFontSmoothing": { + "label": "Εξομάλυνση γραμματοσειράς", + "description": "Ενεργοποίηση απόδοσης γραμματοσειράς ClearType" + }, + "enableDesktopComposition": { + "label": "Σύνθεση επιφάνειας εργασίας", + "description": "Ενεργοποίηση εφέ Aero glass" + }, + "disableAudio": { + "label": "Απενεργοποίηση ήχου", + "description": "Σίγαση όλου του ήχου από την απομακρυσμένη συνεδρία" + }, + "enablePrinting": { + "label": "Ενεργοποίηση εκτύπωσης", + "description": "Ανακατεύθυνση τοπικών εκτυπωτών στην απομακρυσμένη συνεδρία" + }, + "enableDrive": { + "label": "Ενεργοποίηση ανακατεύθυνσης μονάδας δίσκου", + "description": "Αντιστοίχιση τοπικού φακέλου ως μονάδας δίσκου στην απομακρυσμένη συνεδρία" + }, + "disableCopy": { + "label": "Απενεργοποίηση αντιγραφής", + "description": "Αποτροπή αντιγραφής κειμένου από την απομακρυσμένη συνεδρία" + }, + "disablePaste": { + "label": "Απενεργοποίηση επικόλλησης", + "description": "Αποτροπή επικόλλησης κειμένου στην απομακρυσμένη συνεδρία" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/es_ES.json b/plugins/remote-desktop/locales/translated/es_ES.json index 8295dfd97..8ff80cc7b 100644 --- a/plugins/remote-desktop/locales/translated/es_ES.json +++ b/plugins/remote-desktop/locales/translated/es_ES.json @@ -204,11 +204,75 @@ "fontName": "Nombre de fuente", "fontSize": "Tamaño de fuente", "colorScheme": "Esquema de color", - "backspaceKey": "Tecla de retroceso" - } + "backspaceKey": "Tecla de retroceso", + "colorDepth": "Profundidad de color", + "resizeMethod": "Método de redimensionamiento" + }, + "remoteDesktop": "Escritorio remoto", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Red virtual", + "unencryptedShell": "Shell sin cifrar" }, "onboarding": { "feature_desktop": "Escritorio remoto", "feature_desktop_desc": "Abre sesiones RDP, VNC y Telnet en una pestaña, igual que con SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Profundidad de color" + }, + "resizeMethod": { + "label": "Método de redimensionamiento" + }, + "resizeDisplayUpdate": "Actualización de pantalla", + "resizeReconnect": "Reconectar", + "inherit": "Heredar", + "on": "En", + "off": "Apagado", + "forceLossless": { + "label": "Forzar sin pérdida", + "description": "Forzar codificación de imagen sin pérdida (más calidad, más ancho de banda)" + }, + "enableWallpaper": { + "label": "Fondo de pantalla", + "description": "Mostrar fondo de escritorio (desactivarlo mejora el rendimiento)" + }, + "enableFontSmoothing": { + "label": "Suavizado de fuentes", + "description": "Activar renderizado de fuentes ClearType" + }, + "enableDesktopComposition": { + "label": "Composición de escritorio", + "description": "Activar efectos Aero Glass" + }, + "disableAudio": { + "label": "Desactivar audio", + "description": "Silenciar todo el audio de la sesión remota" + }, + "enablePrinting": { + "label": "Activar impresión", + "description": "Redirigir las impresoras locales a la sesión remota" + }, + "enableDrive": { + "label": "Activar redirección de unidades", + "description": "Asignar una carpeta local como una unidad en la sesión remota" + }, + "disableCopy": { + "label": "Deshabilitar copia", + "description": "Impedir la copia de texto de la sesión remota" + }, + "disablePaste": { + "label": "Deshabilitar pegado", + "description": "Impedir el pegado de texto en la sesión remota" + } + }, + "admin": { + "guacdUrl": { + "label": "URL de guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/fi_FI.json b/plugins/remote-desktop/locales/translated/fi_FI.json index f00bdc40f..7f7d5e0ab 100644 --- a/plugins/remote-desktop/locales/translated/fi_FI.json +++ b/plugins/remote-desktop/locales/translated/fi_FI.json @@ -204,11 +204,75 @@ "fontName": "Fontin nimi", "fontSize": "Fonttikoko", "colorScheme": "Värimaailma", - "backspaceKey": "Askelpalautin-näppäin" - } + "backspaceKey": "Askelpalautin-näppäin", + "colorDepth": "Värisyvyys", + "resizeMethod": "Koon muuttamismenetelmä" + }, + "remoteDesktop": "Etätyöpöytä", + "tabRdp": "Maaseudun kehittämisohjelma", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuaaliverkko", + "unencryptedShell": "Salaamaton kuori" }, "onboarding": { "feature_desktop": "Etätyöpöytä", "feature_desktop_desc": "Avaa RDP-, VNC- ja Telnet-istunnot välilehdellä, samalla tavalla kuin SSH:ssa." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Värisyvyys" + }, + "resizeMethod": { + "label": "Koon muuttamismenetelmä" + }, + "resizeDisplayUpdate": "Näyttöpäivitys", + "resizeReconnect": "Yhdistä uudelleen", + "inherit": "Periä", + "on": "Päällä", + "off": "Pois", + "forceLossless": { + "label": "Pakota häviötön", + "description": "Pakota häviötön kuvan koodaus (korkeampi laatu, enemmän kaistanleveyttä)" + }, + "enableWallpaper": { + "label": "Taustakuva", + "description": "Näytä työpöydän taustakuva (poistaminen käytöstä parantaa suorituskykyä)" + }, + "enableFontSmoothing": { + "label": "Fontin pehmennys", + "description": "Ota ClearType-fonttien renderöinti käyttöön" + }, + "enableDesktopComposition": { + "label": "Työpöydän sommittelu", + "description": "Ota Aero Glass -tehosteet käyttöön" + }, + "disableAudio": { + "label": "Poista ääni käytöstä", + "description": "Mykistä kaikki etäistunnon äänet" + }, + "enablePrinting": { + "label": "Ota tulostus käyttöön", + "description": "Ohjaa paikalliset tulostimet etäistuntoon" + }, + "enableDrive": { + "label": "Ota aseman uudelleenohjaus käyttöön", + "description": "Yhdistä paikallinen kansio asemaksi etäistunnossa" + }, + "disableCopy": { + "label": "Poista kopiointi käytöstä", + "description": "Estä tekstin kopioiminen etäistunnosta" + }, + "disablePaste": { + "label": "Poista liittäminen käytöstä", + "description": "Estä tekstin liittäminen etäistuntoon" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd-URL-osoite" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/fr_FR.json b/plugins/remote-desktop/locales/translated/fr_FR.json index 25a456a34..9ce0b3e45 100644 --- a/plugins/remote-desktop/locales/translated/fr_FR.json +++ b/plugins/remote-desktop/locales/translated/fr_FR.json @@ -204,11 +204,75 @@ "fontName": "Nom de la police", "fontSize": "Taille de la police", "colorScheme": "Schéma de couleurs", - "backspaceKey": "Touche Backspace" - } + "backspaceKey": "Touche Backspace", + "colorDepth": "Profondeur de couleur", + "resizeMethod": "Méthode de redimensionnement" + }, + "remoteDesktop": "Bureau à distance", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Réseau virtuel", + "unencryptedShell": "Shell non chiffré" }, "onboarding": { "feature_desktop": "Bureau à distance", "feature_desktop_desc": "Ouvrez les sessions RDP, VNC et Telnet dans un onglet, de la même manière que SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Profondeur de couleur" + }, + "resizeMethod": { + "label": "Méthode de redimensionnement" + }, + "resizeDisplayUpdate": "Mise à jour de l'affichage", + "resizeReconnect": "Reconnect", + "inherit": "Hériter", + "on": "Sur", + "off": "Désactivé", + "forceLossless": { + "label": "Forcer sans perte", + "description": "Forcer l'encodage d'image sans perte (meilleure qualité, plus de bande passante)" + }, + "enableWallpaper": { + "label": "Fond d'écran", + "description": "Afficher le fond d'écran du bureau (désactiver cette option améliore les performances)" + }, + "enableFontSmoothing": { + "label": "Lissage des polices", + "description": "Activer le rendu des polices ClearType" + }, + "enableDesktopComposition": { + "label": "Composition du bureau", + "description": "Activer les effets Aero Glass" + }, + "disableAudio": { + "label": "Désactiver l'audio", + "description": "Couper tout le son de la session distante" + }, + "enablePrinting": { + "label": "Activer l'impression", + "description": "Rediriger les imprimantes locales vers la session distante" + }, + "enableDrive": { + "label": "Activer la redirection de lecteur", + "description": "Monter un dossier local en tant que lecteur dans la session distante" + }, + "disableCopy": { + "label": "Désactiver la copie", + "description": "Empêcher la copie de texte depuis la session distante" + }, + "disablePaste": { + "label": "Désactiver le collage", + "description": "Empêcher le collage de texte dans la session distante" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/he_IL.json b/plugins/remote-desktop/locales/translated/he_IL.json index 154527568..138621c32 100644 --- a/plugins/remote-desktop/locales/translated/he_IL.json +++ b/plugins/remote-desktop/locales/translated/he_IL.json @@ -204,11 +204,75 @@ "fontName": "שם הגופן", "fontSize": "גודל גופן", "colorScheme": "ערכת צבעים", - "backspaceKey": "מקש Backspace" - } + "backspaceKey": "מקש Backspace", + "colorDepth": "עומק צבע", + "resizeMethod": "שיטת שינוי גודל" + }, + "remoteDesktop": "שולחן עבודה מרוחק", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "טלנט", + "virtualNetwork": "רשת וירטואלית", + "unencryptedShell": "מעטפת לא מוצפנת" }, "onboarding": { "feature_desktop": "שולחן עבודה מרוחק", "feature_desktop_desc": "פתחו הפעלות RDP, VNC ו-Telnet בכרטיסייה, בדומה ל-SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "עומק צבע" + }, + "resizeMethod": { + "label": "שיטת שינוי גודל" + }, + "resizeDisplayUpdate": "עדכון תצוגה", + "resizeReconnect": "התחבר מחדש", + "inherit": "לָרֶשֶׁת", + "on": "עַל", + "off": "כבוי", + "forceLossless": { + "label": "כפיית אובדן נתונים", + "description": "כפיית קידוד תמונה ללא אובדן נתונים (איכות גבוהה יותר, רוחב פס גדול יותר)" + }, + "enableWallpaper": { + "label": "טָפֵּט", + "description": "הצגת טפט שולחן עבודה (השבתה משפרת את הביצועים)" + }, + "enableFontSmoothing": { + "label": "החלקת גופנים", + "description": "הפעלת עיבוד גופני ClearType" + }, + "enableDesktopComposition": { + "label": "קומפוזיציה לשולחן העבודה", + "description": "הפעלת אפקטים של Aero Glass" + }, + "disableAudio": { + "label": "השבתת שמע", + "description": "השתקת כל האודיו מההפעלה המרוחקת" + }, + "enablePrinting": { + "label": "הפעל הדפסה", + "description": "ניתוב מחדש של מדפסות מקומיות להפעלה מרוחקת" + }, + "enableDrive": { + "label": "הפעל ניתוב מחדש של כונן", + "description": "מיפוי תיקייה מקומית ככונן בהפעלה מרוחקת" + }, + "disableCopy": { + "label": "השבתת העתקה", + "description": "מניעת העתקת טקסט מההפעלה המרוחקת" + }, + "disablePaste": { + "label": "השבתת הדבקה", + "description": "מניעת הדבקת טקסט לתוך ההפעלה המרוחקת" + } + }, + "admin": { + "guacdUrl": { + "label": "כתובת אתר של guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/hi_IN.json b/plugins/remote-desktop/locales/translated/hi_IN.json index a02f49154..ae056109e 100644 --- a/plugins/remote-desktop/locales/translated/hi_IN.json +++ b/plugins/remote-desktop/locales/translated/hi_IN.json @@ -204,11 +204,75 @@ "fontName": "फ़ॉन्ट नाम", "fontSize": "फ़ॉन्ट आकार", "colorScheme": "रंग योजना", - "backspaceKey": "बैकस्पेस कुंजी" - } + "backspaceKey": "बैकस्पेस कुंजी", + "colorDepth": "रंग गहराई", + "resizeMethod": "रीसाइज़ विधि" + }, + "remoteDesktop": "दूरस्थ डेस्कटॉप", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "वर्चुअल नेटवर्क", + "unencryptedShell": "अनएन्क्रिप्टेड शेल" }, "onboarding": { "feature_desktop": "दूरवर्ती डेस्कटॉप", "feature_desktop_desc": "RDP, VNC और Telnet सेशन को SSH की तरह ही एक टैब में खोलें।" + }, + "settings": { + "user": { + "colorDepth": { + "label": "रंग गहराई" + }, + "resizeMethod": { + "label": "रीसाइज़ विधि" + }, + "resizeDisplayUpdate": "डिस्प्ले अपडेट", + "resizeReconnect": "रिकनेक्ट", + "inherit": "इनहेरिट", + "on": "पर", + "off": "बंद", + "forceLossless": { + "label": "दोषरहित बाध्य करें", + "description": "दोषरहित छवि एन्कोडिंग बाध्य करें (उच्च गुणवत्ता, अधिक बैंडविड्थ)" + }, + "enableWallpaper": { + "label": "वॉलपेपर", + "description": "डेस्कटॉप वॉलपेपर दिखाएं (अक्षम करने से प्रदर्शन बेहतर होता है)" + }, + "enableFontSmoothing": { + "label": "फ़ॉन्ट स्मूथिंग", + "description": "ClearType फ़ॉन्ट रेंडरिंग सक्षम करें" + }, + "enableDesktopComposition": { + "label": "डेस्कटॉप कंपोज़िशन", + "description": "Aero ग्लास प्रभाव सक्षम करें" + }, + "disableAudio": { + "label": "ऑडियो अक्षम करें", + "description": "दूरस्थ सत्र से सभी ऑडियो म्यूट करें" + }, + "enablePrinting": { + "label": "प्रिंटिंग सक्षम करें", + "description": "स्थानीय प्रिंटर को दूरस्थ सत्र में पुनर्निर्देशित करें" + }, + "enableDrive": { + "label": "ड्राइव पुनर्निर्देशन सक्षम करें", + "description": "स्थानीय फ़ोल्डर को दूरस्थ सत्र में ड्राइव के रूप में मैप करें" + }, + "disableCopy": { + "label": "कॉपी अक्षम करें", + "description": "रिमोट सत्र से टेक्स्ट कॉपी करना रोकें" + }, + "disablePaste": { + "label": "पेस्ट अक्षम करें", + "description": "रिमोट सत्र में टेक्स्ट पेस्ट करना रोकें" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/hu_HU.json b/plugins/remote-desktop/locales/translated/hu_HU.json index 336aa82ef..fe5ce1a82 100644 --- a/plugins/remote-desktop/locales/translated/hu_HU.json +++ b/plugins/remote-desktop/locales/translated/hu_HU.json @@ -204,11 +204,75 @@ "fontName": "Betűtípus neve", "fontSize": "Betűméret", "colorScheme": "Színséma", - "backspaceKey": "Visszatörlés billentyű" - } + "backspaceKey": "Visszatörlés billentyű", + "colorDepth": "Színmélység", + "resizeMethod": "Átméretezési módszer" + }, + "remoteDesktop": "Távoli asztal", + "tabRdp": "Vidékfejlesztési Program", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuális hálózat", + "unencryptedShell": "Titkosítatlan shell" }, "onboarding": { "feature_desktop": "Távoli asztal", "feature_desktop_desc": "Nyisson meg RDP, VNC és Telnet munkameneteket egy lapon, ugyanúgy, mint az SSH-n." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Színmélység" + }, + "resizeMethod": { + "label": "Átméretezési módszer" + }, + "resizeDisplayUpdate": "Kijelzőfrissítés", + "resizeReconnect": "Újracsatlakozás", + "inherit": "Örököl", + "on": "On", + "off": "Le", + "forceLossless": { + "label": "Veszteségmentes erő", + "description": "Veszteségmentes képkódolás kényszerítése (jobb minőség, nagyobb sávszélesség)" + }, + "enableWallpaper": { + "label": "Tapéta", + "description": "Asztali háttérkép megjelenítése (a letiltás javítja a teljesítményt)" + }, + "enableFontSmoothing": { + "label": "Betűsimítás", + "description": "ClearType betűtípus-megjelenítés engedélyezése" + }, + "enableDesktopComposition": { + "label": "Asztali kompozíció", + "description": "Aero Glass effektek engedélyezése" + }, + "disableAudio": { + "label": "Hang letiltása", + "description": "Némítsa el az összes hangot a távoli munkamenetből" + }, + "enablePrinting": { + "label": "Nyomtatás engedélyezése", + "description": "Helyi nyomtatók átirányítása a távoli munkamenetre" + }, + "enableDrive": { + "label": "Meghajtó átirányításának engedélyezése", + "description": "Helyi mappa meghajtóként való leképezése a távoli munkamenetben" + }, + "disableCopy": { + "label": "Másolás letiltása", + "description": "Szöveg másolásának megakadályozása a távoli munkamenetből" + }, + "disablePaste": { + "label": "Beillesztés letiltása", + "description": "Szöveg beillesztésének megakadályozása a távoli munkamenetbe" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/id_ID.json b/plugins/remote-desktop/locales/translated/id_ID.json index 7b01e8448..eae6679d4 100644 --- a/plugins/remote-desktop/locales/translated/id_ID.json +++ b/plugins/remote-desktop/locales/translated/id_ID.json @@ -204,11 +204,75 @@ "fontName": "Nama Font", "fontSize": "Ukuran Huruf", "colorScheme": "Skema Warna", - "backspaceKey": "Tombol Backspace" - } + "backspaceKey": "Tombol Backspace", + "colorDepth": "Kedalaman Warna", + "resizeMethod": "Metode Pengubahan Ukuran" + }, + "remoteDesktop": "Desktop Jarak Jauh", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Jaringan Virtual", + "unencryptedShell": "Shell yang tidak terenkripsi" }, "onboarding": { "feature_desktop": "Desktop jarak jauh", "feature_desktop_desc": "Buka sesi RDP, VNC, dan Telnet dalam satu tab, sama seperti SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Kedalaman Warna" + }, + "resizeMethod": { + "label": "Metode Pengubahan Ukuran" + }, + "resizeDisplayUpdate": "Pembaruan Tampilan", + "resizeReconnect": "Terhubung kembali", + "inherit": "Mewarisi", + "on": "Pada", + "off": "Mati", + "forceLossless": { + "label": "Paksa Tanpa Kehilangan", + "description": "Paksa pengkodean gambar tanpa kehilangan data (kualitas lebih tinggi, bandwidth lebih besar)" + }, + "enableWallpaper": { + "label": "Wallpaper", + "description": "Tampilkan wallpaper desktop (menonaktifkannya akan meningkatkan kinerja)" + }, + "enableFontSmoothing": { + "label": "Penghalusan Font", + "description": "Aktifkan rendering font ClearType" + }, + "enableDesktopComposition": { + "label": "Komposisi Desktop", + "description": "Aktifkan efek kaca Aero" + }, + "disableAudio": { + "label": "Nonaktifkan Audio", + "description": "Nonaktifkan semua audio dari sesi jarak jauh." + }, + "enablePrinting": { + "label": "Aktifkan Pencetakan", + "description": "Alihkan printer lokal ke sesi jarak jauh." + }, + "enableDrive": { + "label": "Aktifkan Pengalihan Drive", + "description": "Petakan folder lokal sebagai drive di sesi jarak jauh." + }, + "disableCopy": { + "label": "Nonaktifkan Salin", + "description": "Mencegah penyalinan teks dari sesi jarak jauh" + }, + "disablePaste": { + "label": "Nonaktifkan Tempel", + "description": "Mencegah penyisipan teks ke dalam sesi jarak jauh" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/it_IT.json b/plugins/remote-desktop/locales/translated/it_IT.json index bfc53ce6d..341e77543 100644 --- a/plugins/remote-desktop/locales/translated/it_IT.json +++ b/plugins/remote-desktop/locales/translated/it_IT.json @@ -204,11 +204,75 @@ "fontName": "Nome font", "fontSize": "Dimensione font", "colorScheme": "Schema colori", - "backspaceKey": "Tasto Backspace" - } + "backspaceKey": "Tasto Backspace", + "colorDepth": "Profondità colore", + "resizeMethod": "Metodo di ridimensionamento" + }, + "remoteDesktop": "Desktop remoto", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Rete virtuale", + "unencryptedShell": "Shell non crittografata" }, "onboarding": { "feature_desktop": "Desktop remoto", "feature_desktop_desc": "Apri le sessioni RDP, VNC e Telnet in una scheda, proprio come per SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Profondità colore" + }, + "resizeMethod": { + "label": "Metodo di ridimensionamento" + }, + "resizeDisplayUpdate": "Aggiornamento della visualizzazione", + "resizeReconnect": "Ricollega", + "inherit": "Ereditare", + "on": "SU", + "off": "Spento", + "forceLossless": { + "label": "Forza lossless", + "description": "Forza la codifica lossless delle immagini (qualità più alta, maggiore utilizzo di banda)" + }, + "enableWallpaper": { + "label": "Sfondo", + "description": "Mostra lo sfondo del desktop (disabilitarlo migliora le prestazioni)" + }, + "enableFontSmoothing": { + "label": "Smussamento caratteri", + "description": "Abilita il rendering dei caratteri ClearType" + }, + "enableDesktopComposition": { + "label": "Composizione desktop", + "description": "Abilita gli effetti Aero Glass" + }, + "disableAudio": { + "label": "Disabilita audio", + "description": "Disattiva tutto l'audio dalla sessione remota" + }, + "enablePrinting": { + "label": "Abilita stampa", + "description": "Reindirizza le stampanti locali alla sessione remota" + }, + "enableDrive": { + "label": "Abilita reindirizzamento unità", + "description": "Mappa una cartella locale come unità nella sessione remota" + }, + "disableCopy": { + "label": "Disabilita copia", + "description": "Impedisce la copia di testo dalla sessione remota" + }, + "disablePaste": { + "label": "Disabilita incolla", + "description": "Impedisce di incollare testo nella sessione remota" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/ja_JP.json b/plugins/remote-desktop/locales/translated/ja_JP.json index 763e54814..3d19d7639 100644 --- a/plugins/remote-desktop/locales/translated/ja_JP.json +++ b/plugins/remote-desktop/locales/translated/ja_JP.json @@ -204,11 +204,75 @@ "fontName": "フォント名", "fontSize": "フォントサイズ", "colorScheme": "カラースキーム", - "backspaceKey": "バックスペースキー" - } + "backspaceKey": "バックスペースキー", + "colorDepth": "色深度", + "resizeMethod": "リサイズ方法" + }, + "remoteDesktop": "リモートデスクトップ", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "仮想ネットワーク", + "unencryptedShell": "暗号化されていないシェル" }, "onboarding": { "feature_desktop": "リモートデスクトップ", "feature_desktop_desc": "RDP、VNC、Telnetのセッションは、SSHと同様にタブで開きます。" + }, + "settings": { + "user": { + "colorDepth": { + "label": "色深度" + }, + "resizeMethod": { + "label": "リサイズ方法" + }, + "resizeDisplayUpdate": "ディスプレイの更新", + "resizeReconnect": "再接続", + "inherit": "継承する", + "on": "の上", + "off": "オフ", + "forceLossless": { + "label": "ロスレスを強制", + "description": "ロスレス画像エンコーディングを強制する(高品質だが帯域幅を多く消費)" + }, + "enableWallpaper": { + "label": "壁紙", + "description": "デスクトップ壁紙を表示する(無効にするとパフォーマンスが向上)" + }, + "enableFontSmoothing": { + "label": "フォントスムージング", + "description": "ClearTypeフォントレンダリングを有効にする" + }, + "enableDesktopComposition": { + "label": "デスクトップコンポジション", + "description": "Aero Glass効果を有効にする" + }, + "disableAudio": { + "label": "オーディオを無効化", + "description": "リモートセッションの音声をすべてミュートする" + }, + "enablePrinting": { + "label": "印刷を有効化", + "description": "ローカルプリンターをリモートセッションにリダイレクトする" + }, + "enableDrive": { + "label": "ドライブリダイレクションを有効化", + "description": "リモートセッションでローカルフォルダーをドライブとしてマッピングする" + }, + "disableCopy": { + "label": "コピーを無効にする", + "description": "リモートセッションからのテキストコピーを禁止します" + }, + "disablePaste": { + "label": "貼り付けを無効にする", + "description": "リモートセッションへのテキスト貼り付けを禁止します" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/ko_KR.json b/plugins/remote-desktop/locales/translated/ko_KR.json index 71762195a..542b4417f 100644 --- a/plugins/remote-desktop/locales/translated/ko_KR.json +++ b/plugins/remote-desktop/locales/translated/ko_KR.json @@ -204,11 +204,75 @@ "fontName": "글꼴 이름", "fontSize": "글꼴 크기", "colorScheme": "색 구성표", - "backspaceKey": "백스페이스 키" - } + "backspaceKey": "백스페이스 키", + "colorDepth": "색 농도", + "resizeMethod": "크기 조정 방법" + }, + "remoteDesktop": "원격 데스크톱", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "텔넷", + "virtualNetwork": "가상 네트워크", + "unencryptedShell": "암호화되지 않은 셸" }, "onboarding": { "feature_desktop": "원격 데스크톱", "feature_desktop_desc": "SSH와 마찬가지로 RDP, VNC 및 Telnet 세션을 탭에서 열 수 있습니다." + }, + "settings": { + "user": { + "colorDepth": { + "label": "색 농도" + }, + "resizeMethod": { + "label": "크기 조정 방법" + }, + "resizeDisplayUpdate": "디스플레이 업데이트", + "resizeReconnect": "다시 연결", + "inherit": "상속하다", + "on": "~에", + "off": "끄다", + "forceLossless": { + "label": "무손실 강제", + "description": "무손실 이미지 인코딩 강제 (더 높은 품질, 더 많은 대역폭)" + }, + "enableWallpaper": { + "label": "배경 화면", + "description": "바탕 화면 배경 표시 (비활성화 시 성능 향상)" + }, + "enableFontSmoothing": { + "label": "글꼴 다듬기", + "description": "ClearType 글꼴 렌더링 사용" + }, + "enableDesktopComposition": { + "label": "데스크톱 구성", + "description": "Aero 글래스 효과 활성화" + }, + "disableAudio": { + "label": "오디오 비활성화", + "description": "원격 세션의 모든 오디오 음소거" + }, + "enablePrinting": { + "label": "인쇄 활성화", + "description": "로컬 프린터를 원격 세션으로 리디렉션" + }, + "enableDrive": { + "label": "드라이브 리디렉션 활성화", + "description": "로컬 폴더를 원격 세션에서 드라이브로 매핑" + }, + "disableCopy": { + "label": "복사 비활성화", + "description": "원격 세션에서 텍스트 복사 방지" + }, + "disablePaste": { + "label": "붙여넣기 비활성화", + "description": "원격 세션으로 텍스트 붙여넣기 방지" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/nl_NL.json b/plugins/remote-desktop/locales/translated/nl_NL.json index 275f445bb..d24adb82b 100644 --- a/plugins/remote-desktop/locales/translated/nl_NL.json +++ b/plugins/remote-desktop/locales/translated/nl_NL.json @@ -204,11 +204,75 @@ "fontName": "Lettertypenaam", "fontSize": "Lettergrootte", "colorScheme": "Kleurenschema", - "backspaceKey": "Backspace-toets" - } + "backspaceKey": "Backspace-toets", + "colorDepth": "Kleurdiepte", + "resizeMethod": "Formaatwijzigingsmethode" + }, + "remoteDesktop": "Extern bureaublad", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtueel netwerk", + "unencryptedShell": "Niet-versleutelde shell" }, "onboarding": { "feature_desktop": "Extern bureaublad", "feature_desktop_desc": "Open RDP-, VNC- en Telnet-sessies in een tabblad, net als bij SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Kleurdiepte" + }, + "resizeMethod": { + "label": "Formaatwijzigingsmethode" + }, + "resizeDisplayUpdate": "Weergave bijwerken", + "resizeReconnect": "Opnieuw verbinding maken", + "inherit": "Erven", + "on": "Op", + "off": "Uit", + "forceLossless": { + "label": "Krachtverliesloos", + "description": "Forceer verliesvrije beeldcodering (hogere kwaliteit, meer bandbreedte)" + }, + "enableWallpaper": { + "label": "Behang", + "description": "Toon bureaubladachtergrond (uitschakelen verbetert de prestaties)" + }, + "enableFontSmoothing": { + "label": "Lettertype-vloeiendheid", + "description": "Schakel ClearType-lettertypeweergave in" + }, + "enableDesktopComposition": { + "label": "Bureaubladcompositie", + "description": "Schakel Aero-glaseffecten in" + }, + "disableAudio": { + "label": "Geluid uitschakelen", + "description": "Demp alle audio van de externe sessie." + }, + "enablePrinting": { + "label": "Afdrukken inschakelen", + "description": "Lokale printers doorverwijzen naar de externe sessie" + }, + "enableDrive": { + "label": "Schakel schijfomleiding in", + "description": "Een lokale map toewijzen als schijfstation in de externe sessie." + }, + "disableCopy": { + "label": "Kopiëren uitschakelen", + "description": "Voorkom het kopiëren van tekst vanuit de externe sessie." + }, + "disablePaste": { + "label": "Plakken uitschakelen", + "description": "Voorkom dat tekst in de externe sessie wordt geplakt." + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/no_NO.json b/plugins/remote-desktop/locales/translated/no_NO.json index ba4130e8c..343a53bb9 100644 --- a/plugins/remote-desktop/locales/translated/no_NO.json +++ b/plugins/remote-desktop/locales/translated/no_NO.json @@ -204,11 +204,75 @@ "fontName": "Skriftnavn", "fontSize": "Skriftstørrelse", "colorScheme": "Fargeskjema", - "backspaceKey": "Tilbaketast" - } + "backspaceKey": "Tilbaketast", + "colorDepth": "Fargedybde", + "resizeMethod": "Endre størrelse-metoden" + }, + "remoteDesktop": "Eksternt skrivebord", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuelt nettverk", + "unencryptedShell": "Ukryptert skall" }, "onboarding": { "feature_desktop": "Eksternt skrivebord", "feature_desktop_desc": "Åpne RDP-, VNC- og Telnet-økter i en fane, på samme måte som SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Fargedybde" + }, + "resizeMethod": { + "label": "Endre størrelse-metoden" + }, + "resizeDisplayUpdate": "Oppdatering av skjerm", + "resizeReconnect": "Koble til igjen", + "inherit": "Arve", + "on": "På", + "off": "Av", + "forceLossless": { + "label": "Tving tapsfri", + "description": "Tving frem tapsfri bildekoding (høyere kvalitet, mer båndbredde)" + }, + "enableWallpaper": { + "label": "Bakgrunn", + "description": "Vis skrivebordsbakgrunn (deaktivering forbedrer ytelsen)" + }, + "enableFontSmoothing": { + "label": "Skriftutjevning", + "description": "Aktiver gjengivelse av ClearType-skrifttyper" + }, + "enableDesktopComposition": { + "label": "Skrivebordskomposisjon", + "description": "Aktiver Aero Glass-effekter" + }, + "disableAudio": { + "label": "Deaktiver lyd", + "description": "Demp all lyd fra den eksterne økten" + }, + "enablePrinting": { + "label": "Aktiver utskrift", + "description": "Omdiriger lokale skrivere til den eksterne økten" + }, + "enableDrive": { + "label": "Aktiver omdirigering av stasjoner", + "description": "Tilordne en lokal mappe som en stasjon i den eksterne økten" + }, + "disableCopy": { + "label": "Deaktiver kopiering", + "description": "Forhindre kopiering av tekst fra den eksterne økten" + }, + "disablePaste": { + "label": "Deaktiver liming", + "description": "Forhindre innliming av tekst i den eksterne økten" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd-URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/pl_PL.json b/plugins/remote-desktop/locales/translated/pl_PL.json index 33177ca19..2b1e4ae62 100644 --- a/plugins/remote-desktop/locales/translated/pl_PL.json +++ b/plugins/remote-desktop/locales/translated/pl_PL.json @@ -204,11 +204,75 @@ "fontName": "Nazwa czcionki", "fontSize": "Rozmiar czcionki", "colorScheme": "Schemat kolorów", - "backspaceKey": "Klawisz Backspace" - } + "backspaceKey": "Klawisz Backspace", + "colorDepth": "Głębia koloru", + "resizeMethod": "Metoda zmiany rozmiaru" + }, + "remoteDesktop": "Pulpit zdalny", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Sieć wirtualna", + "unencryptedShell": "Niezaszyfrowana powłoka" }, "onboarding": { "feature_desktop": "Zdalny pulpit", "feature_desktop_desc": "Otwieraj sesje RDP, VNC i Telnet w karcie, tak samo jak SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Głębia koloru" + }, + "resizeMethod": { + "label": "Metoda zmiany rozmiaru" + }, + "resizeDisplayUpdate": "Aktualizacja ekranu", + "resizeReconnect": "Połącz ponownie", + "inherit": "Dziedzicz", + "on": "Wł.", + "off": "Wył.", + "forceLossless": { + "label": "Wymuś bezstratność", + "description": "Wymuś bezstratne kodowanie obrazu (wyższa jakość, większa przepustowość)" + }, + "enableWallpaper": { + "label": "Tapeta", + "description": "Pokaż tapetę pulpitu (wyłączenie poprawia wydajność)" + }, + "enableFontSmoothing": { + "label": "Wygładzanie czcionek", + "description": "Włącz renderowanie czcionek ClearType" + }, + "enableDesktopComposition": { + "label": "Kompozycja pulpitu", + "description": "Włącz efekty Aero Glass" + }, + "disableAudio": { + "label": "Wyłącz dźwięk", + "description": "Wycisz cały dźwięk z sesji zdalnej" + }, + "enablePrinting": { + "label": "Włącz drukowanie", + "description": "Przekieruj drukarki lokalne do sesji zdalnej" + }, + "enableDrive": { + "label": "Włącz przekierowanie dysku", + "description": "Mapuj folder lokalny jako dysk w sesji zdalnej" + }, + "disableCopy": { + "label": "Wyłącz kopiowanie", + "description": "Zapobiegaj kopiowaniu tekstu z sesji zdalnej" + }, + "disablePaste": { + "label": "Wyłącz wklejanie", + "description": "Zapobiegaj wklejaniu tekstu do sesji zdalnej" + } + }, + "admin": { + "guacdUrl": { + "label": "Adres URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/pt_BR.json b/plugins/remote-desktop/locales/translated/pt_BR.json index 3b01f1470..aefa99433 100644 --- a/plugins/remote-desktop/locales/translated/pt_BR.json +++ b/plugins/remote-desktop/locales/translated/pt_BR.json @@ -204,11 +204,75 @@ "fontName": "Nome da fonte", "fontSize": "Tamanho da fonte", "colorScheme": "Esquema de cores", - "backspaceKey": "Tecla Backspace" - } + "backspaceKey": "Tecla Backspace", + "colorDepth": "Profundidade de cor", + "resizeMethod": "Método de redimensionamento" + }, + "remoteDesktop": "Área de Trabalho Remota", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Rede Virtual", + "unencryptedShell": "Shell não criptografado" }, "onboarding": { "feature_desktop": "Área de trabalho remota", "feature_desktop_desc": "Abra sessões RDP, VNC e Telnet em uma aba, da mesma forma que o SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Profundidade de cor" + }, + "resizeMethod": { + "label": "Método de redimensionamento" + }, + "resizeDisplayUpdate": "Atualização de exibição", + "resizeReconnect": "Reconectar", + "inherit": "Herdar", + "on": "Sobre", + "off": "Desligado", + "forceLossless": { + "label": "Força sem perdas", + "description": "Forçar codificação de imagem sem perdas (maior qualidade, mais largura de banda)" + }, + "enableWallpaper": { + "label": "Papel de parede", + "description": "Exibir papel de parede da área de trabalho (desativar melhora o desempenho)" + }, + "enableFontSmoothing": { + "label": "Suavização de fontes", + "description": "Ativar renderização de fontes ClearType" + }, + "enableDesktopComposition": { + "label": "Composição de mesa", + "description": "Ative os efeitos de vidro Aero" + }, + "disableAudio": { + "label": "Desativar áudio", + "description": "Silenciar todo o áudio da sessão remota." + }, + "enablePrinting": { + "label": "Habilitar impressão", + "description": "Redirecionar impressoras locais para a sessão remota." + }, + "enableDrive": { + "label": "Ativar o redirecionamento de unidades", + "description": "Mapear uma pasta local como uma unidade na sessão remota." + }, + "disableCopy": { + "label": "Desativar cópia", + "description": "Impedir a cópia de texto da sessão remota" + }, + "disablePaste": { + "label": "Desativar colagem", + "description": "Impedir a colagem de texto na sessão remota." + } + }, + "admin": { + "guacdUrl": { + "label": "URL do guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/pt_PT.json b/plugins/remote-desktop/locales/translated/pt_PT.json index 1493f46a3..c330096a6 100644 --- a/plugins/remote-desktop/locales/translated/pt_PT.json +++ b/plugins/remote-desktop/locales/translated/pt_PT.json @@ -204,11 +204,75 @@ "fontName": "Nome da Fonte", "fontSize": "Tamanho da Fonte", "colorScheme": "Esquema de Cores", - "backspaceKey": "Tecla Backspace" - } + "backspaceKey": "Tecla Backspace", + "colorDepth": "Profundidade de cor", + "resizeMethod": "Método de redimensionamento" + }, + "remoteDesktop": "Ambiente de trabalho remoto", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Rede Virtual", + "unencryptedShell": "Shell não encriptado" }, "onboarding": { "feature_desktop": "Área de trabalho remota", "feature_desktop_desc": "Abra as sessões RDP, VNC e Telnet num separador, da mesma forma que o SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Profundidade de cor" + }, + "resizeMethod": { + "label": "Método de redimensionamento" + }, + "resizeDisplayUpdate": "Atualização de visualização", + "resizeReconnect": "Reconectar", + "inherit": "Herdar", + "on": "Em", + "off": "Desligado", + "forceLossless": { + "label": "Forçar sem perdas", + "description": "Forçar codificação de imagem sem perdas (maior qualidade, mais largura de banda)" + }, + "enableWallpaper": { + "label": "Fundo de ecrã", + "description": "Mostrar fundo de ecrã (desativar melhora o desempenho)" + }, + "enableFontSmoothing": { + "label": "Suavização de Fontes", + "description": "Ativar renderização de fontes ClearType" + }, + "enableDesktopComposition": { + "label": "Composição do ambiente de trabalho", + "description": "Ativar efeitos de transparência Aero" + }, + "disableAudio": { + "label": "Desativar Áudio", + "description": "Silenciar todo o áudio da sessão remota" + }, + "enablePrinting": { + "label": "Ativar Impressão", + "description": "Redirecionar impressoras locais para a sessão remota" + }, + "enableDrive": { + "label": "Ativar Redirecionamento de Unidades", + "description": "Mapear uma pasta local como unidade na sessão remota" + }, + "disableCopy": { + "label": "Desativar Cópia", + "description": "Impedir a cópia de texto da sessão remota" + }, + "disablePaste": { + "label": "Desativar Colagem", + "description": "Impedir a colagem de texto na sessão remota" + } + }, + "admin": { + "guacdUrl": { + "label": "URL do guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/ro_RO.json b/plugins/remote-desktop/locales/translated/ro_RO.json index 1838ecdb3..97c5fa2ea 100644 --- a/plugins/remote-desktop/locales/translated/ro_RO.json +++ b/plugins/remote-desktop/locales/translated/ro_RO.json @@ -204,11 +204,75 @@ "fontName": "Nume font", "fontSize": "Dimensiunea fontului", "colorScheme": "Schemă de culori", - "backspaceKey": "Tasta Backspace" - } + "backspaceKey": "Tasta Backspace", + "colorDepth": "Adâncimea culorii", + "resizeMethod": "Metodă de redimensionare" + }, + "remoteDesktop": "Desktop la distanță", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Rețea virtuală", + "unencryptedShell": "Shell necriptat" }, "onboarding": { "feature_desktop": "Desktop la distanță", "feature_desktop_desc": "Deschideți sesiuni RDP, VNC și Telnet într-o filă, la fel ca în cazul SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Adâncimea culorii" + }, + "resizeMethod": { + "label": "Metodă de redimensionare" + }, + "resizeDisplayUpdate": "Actualizare afișare", + "resizeReconnect": "Reconectați-vă", + "inherit": "Moşteni", + "on": "Pe", + "off": "Dezactivat", + "forceLossless": { + "label": "Forța fără pierderi", + "description": "Forțează codificarea imaginilor fără pierderi (calitate mai mare, lățime de bandă mai mare)" + }, + "enableWallpaper": { + "label": "Tapet", + "description": "Afișează imaginea de fundal a desktopului (dezactivarea îmbunătățește performanța)" + }, + "enableFontSmoothing": { + "label": "Netezirea fonturilor", + "description": "Activează randarea fonturilor ClearType" + }, + "enableDesktopComposition": { + "label": "Compoziție desktop", + "description": "Activează efectele Aero Glass" + }, + "disableAudio": { + "label": "Dezactivați sunetul", + "description": "Dezactivați tot sunetul din sesiunea la distanță" + }, + "enablePrinting": { + "label": "Activează imprimarea", + "description": "Redirecționează imprimantele locale către sesiunea la distanță" + }, + "enableDrive": { + "label": "Activați redirecționarea unității", + "description": "Maparea unui folder local ca unitate în sesiunea la distanță" + }, + "disableCopy": { + "label": "Dezactivați copierea", + "description": "Împiedicați copierea textului din sesiunea la distanță" + }, + "disablePaste": { + "label": "Dezactivați lipirea", + "description": "Împiedicați lipirea textului în sesiunea la distanță" + } + }, + "admin": { + "guacdUrl": { + "label": "URL-ul guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/ru_RU.json b/plugins/remote-desktop/locales/translated/ru_RU.json index d2d2f3000..a95facd9c 100644 --- a/plugins/remote-desktop/locales/translated/ru_RU.json +++ b/plugins/remote-desktop/locales/translated/ru_RU.json @@ -204,11 +204,75 @@ "fontName": "Название шрифта", "fontSize": "Размер шрифта", "colorScheme": "Цветовая схема", - "backspaceKey": "Клавиша Backspace" - } + "backspaceKey": "Клавиша Backspace", + "colorDepth": "Глубина цвета", + "resizeMethod": "Метод изменения размера" + }, + "remoteDesktop": "Удаленный рабочий стол", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Виртуальная сеть", + "unencryptedShell": "Незашифрованная оболочка" }, "onboarding": { "feature_desktop": "Удаленный рабочий стол", "feature_desktop_desc": "Открывайте сеансы RDP, VNC и Telnet во вкладке, как и SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Глубина цвета" + }, + "resizeMethod": { + "label": "Метод изменения размера" + }, + "resizeDisplayUpdate": "Обновление дисплея", + "resizeReconnect": "Переподключитесь", + "inherit": "Наследовать", + "on": "На", + "off": "Выключенный", + "forceLossless": { + "label": "Принудительно без потерь", + "description": "Принудительное кодирование изображений без потерь (более высокое качество, больший расход трафика)" + }, + "enableWallpaper": { + "label": "Обои", + "description": "Показывать обои рабочего стола (отключение повышает производительность)" + }, + "enableFontSmoothing": { + "label": "Сглаживание шрифтов", + "description": "Включить рендеринг шрифтов ClearType" + }, + "enableDesktopComposition": { + "label": "Композиция рабочего стола", + "description": "Включить эффекты Aero Glass" + }, + "disableAudio": { + "label": "Отключить звук", + "description": "Отключить весь звук в удаленном сеансе" + }, + "enablePrinting": { + "label": "Включить печать", + "description": "Перенаправлять локальные принтеры в удаленный сеанс" + }, + "enableDrive": { + "label": "Включить перенаправление дисков", + "description": "Подключать локальную папку как диск в удаленном сеансе" + }, + "disableCopy": { + "label": "Отключить копирование", + "description": "Запретить копирование текста из удаленного сеанса" + }, + "disablePaste": { + "label": "Отключить вставку", + "description": "Запретить вставку текста в удаленный сеанс" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/sr_SP.json b/plugins/remote-desktop/locales/translated/sr_SP.json index 3616300db..01ccd69d6 100644 --- a/plugins/remote-desktop/locales/translated/sr_SP.json +++ b/plugins/remote-desktop/locales/translated/sr_SP.json @@ -204,11 +204,75 @@ "fontName": "Назив фонта", "fontSize": "Величина фонта", "colorScheme": "Шема боја", - "backspaceKey": "Тастер Backspace" - } + "backspaceKey": "Тастер Backspace", + "colorDepth": "Дубина боје", + "resizeMethod": "Метод промене величине" + }, + "remoteDesktop": "Удаљена радна површина", + "tabRdp": "РДП", + "tabVnc": "VNC", + "tabTelnet": "Телнет", + "virtualNetwork": "Виртуелна мрежа", + "unencryptedShell": "Нешифрована љуска" }, "onboarding": { "feature_desktop": "Удаљена радна површина", "feature_desktop_desc": "Отворите RDP, VNC и Telnet сесије у картици, исто као и SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Дубина боје" + }, + "resizeMethod": { + "label": "Метод промене величине" + }, + "resizeDisplayUpdate": "Ажурирање екрана", + "resizeReconnect": "Поново се повежите", + "inherit": "Наследи", + "on": "Укључено", + "off": "Искључено", + "forceLossless": { + "label": "Присилно без губитака", + "description": "Присилно кодирање слике без губитака (виши квалитет, већи пропусни опсег)" + }, + "enableWallpaper": { + "label": "Позадина", + "description": "Прикажи позадину радне површине (онемогућавање побољшава перформансе)" + }, + "enableFontSmoothing": { + "label": "Изглађивање фонта", + "description": "Омогући приказивање ClearType фонта" + }, + "enableDesktopComposition": { + "label": "Композиција на радној површини", + "description": "Омогући ефекте Aero Glass-а" + }, + "disableAudio": { + "label": "Онемогући звук", + "description": "Искључите сав звук из удаљене сесије" + }, + "enablePrinting": { + "label": "Омогући штампање", + "description": "Преусмерите локалне штампаче на удаљену сесију" + }, + "enableDrive": { + "label": "Омогући преусмеравање диска", + "description": "Мапирајте локалну фасциклу као диск у удаљеној сесији" + }, + "disableCopy": { + "label": "Онемогући копирање", + "description": "Спречите копирање текста из удаљене сесије" + }, + "disablePaste": { + "label": "Онемогући лепљење", + "description": "Спречите лепљење текста у удаљену сесију" + } + }, + "admin": { + "guacdUrl": { + "label": "URL адреса guacd-а" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/sv_SE.json b/plugins/remote-desktop/locales/translated/sv_SE.json index 76ca9559c..f6d5195ab 100644 --- a/plugins/remote-desktop/locales/translated/sv_SE.json +++ b/plugins/remote-desktop/locales/translated/sv_SE.json @@ -204,11 +204,75 @@ "fontName": "Typsnittsnamn", "fontSize": "Fontstorlek", "colorScheme": "Färgschema", - "backspaceKey": "Backstegstangenten" - } + "backspaceKey": "Backstegstangenten", + "colorDepth": "Färgdjup", + "resizeMethod": "Ändra storleksmetod" + }, + "remoteDesktop": "Fjärrskrivbord", + "tabRdp": "Landsbygdsutveckling", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Virtuellt nätverk", + "unencryptedShell": "Okrypterat skal" }, "onboarding": { "feature_desktop": "Fjärrskrivbord", "feature_desktop_desc": "Öppna RDP-, VNC- och Telnet-sessioner i en flik, samma som med SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Färgdjup" + }, + "resizeMethod": { + "label": "Ändra storleksmetod" + }, + "resizeDisplayUpdate": "Uppdatering av displayen", + "resizeReconnect": "Återanslut", + "inherit": "Ärva", + "on": "På", + "off": "Av", + "forceLossless": { + "label": "Tvinga förlustfri", + "description": "Tvinga fram förlustfri bildkodning (högre kvalitet, mer bandbredd)" + }, + "enableWallpaper": { + "label": "Tapet", + "description": "Visa skrivbordsbakgrund (inaktivering förbättrar prestandan)" + }, + "enableFontSmoothing": { + "label": "Utjämning av teckensnitt", + "description": "Aktivera rendering av ClearType-teckensnitt" + }, + "enableDesktopComposition": { + "label": "Skrivbordskomposition", + "description": "Aktivera Aero Glass-effekter" + }, + "disableAudio": { + "label": "Inaktivera ljud", + "description": "Stäng av allt ljud från fjärrsessionen" + }, + "enablePrinting": { + "label": "Aktivera utskrift", + "description": "Omdirigera lokala skrivare till fjärrsessionen" + }, + "enableDrive": { + "label": "Aktivera omdirigering av kördata", + "description": "Mappa en lokal mapp som en enhet i fjärrsessionen" + }, + "disableCopy": { + "label": "Inaktivera kopiering", + "description": "Förhindra kopiering av text från fjärrsessionen" + }, + "disablePaste": { + "label": "Inaktivera inklistring", + "description": "Förhindra att text klistras in i fjärrsessionen" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd-URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/th_TH.json b/plugins/remote-desktop/locales/translated/th_TH.json index aee085e4e..0972086be 100644 --- a/plugins/remote-desktop/locales/translated/th_TH.json +++ b/plugins/remote-desktop/locales/translated/th_TH.json @@ -204,11 +204,75 @@ "fontName": "ชื่อแบบอักษร", "fontSize": "ขนาดตัวอักษร", "colorScheme": "โทนสี", - "backspaceKey": "ปุ่มลบ" - } + "backspaceKey": "ปุ่มลบ", + "colorDepth": "ความลึกของสี", + "resizeMethod": "วิธีการปรับขนาด" + }, + "remoteDesktop": "รีโมทเดสก์ท็อป", + "tabRdp": "อาร์ดีพี", + "tabVnc": "วีเอ็นซี", + "tabTelnet": "เทลเน็ต", + "virtualNetwork": "เครือข่ายเสมือน", + "unencryptedShell": "เชลล์ที่ไม่ได้เข้ารหัส" }, "onboarding": { "feature_desktop": "การเข้าถึงเดสก์ท็อประยะไกล", "feature_desktop_desc": "เปิดเซสชัน RDP, VNC และ Telnet ในแท็บเดียวกัน เหมือนกับการเปิด SSH" + }, + "settings": { + "user": { + "colorDepth": { + "label": "ความลึกของสี" + }, + "resizeMethod": { + "label": "วิธีการปรับขนาด" + }, + "resizeDisplayUpdate": "อัปเดตการแสดงผล", + "resizeReconnect": "เชื่อมต่อใหม่", + "inherit": "สืบทอด", + "on": "บน", + "off": "ปิด", + "forceLossless": { + "label": "บังคับการสูญเสีย", + "description": "บังคับใช้การเข้ารหัสภาพแบบไม่สูญเสียคุณภาพ (คุณภาพสูงกว่า ใช้แบนด์วิดท์มากกว่า)" + }, + "enableWallpaper": { + "label": "วอลเปเปอร์", + "description": "แสดงภาพพื้นหลังเดสก์ท็อป (การปิดใช้งานจะช่วยเพิ่มประสิทธิภาพ)" + }, + "enableFontSmoothing": { + "label": "การปรับความเรียบของแบบอักษร", + "description": "เปิดใช้งานการแสดงผลฟอนต์ ClearType" + }, + "enableDesktopComposition": { + "label": "การจัดองค์ประกอบเดสก์ท็อป", + "description": "เปิดใช้งานเอฟเฟกต์กระจก Aero" + }, + "disableAudio": { + "label": "ปิดใช้งานเสียง", + "description": "ปิดเสียงทั้งหมดจากการเชื่อมต่อระยะไกล" + }, + "enablePrinting": { + "label": "เปิดใช้งานการพิมพ์", + "description": "โอนย้ายเครื่องพิมพ์ในเครื่องไปยังเซสชันระยะไกล" + }, + "enableDrive": { + "label": "เปิดใช้งานการเปลี่ยนเส้นทางไดรฟ์", + "description": "แมปโฟลเดอร์ในเครื่องเป็นไดรฟ์ในเซสชันระยะไกล" + }, + "disableCopy": { + "label": "ปิดใช้งานการคัดลอก", + "description": "ป้องกันการคัดลอกข้อความจากเซสชันระยะไกล" + }, + "disablePaste": { + "label": "ปิดใช้งานการวาง", + "description": "ป้องกันการวางข้อความลงในเซสชันระยะไกล" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/tr_TR.json b/plugins/remote-desktop/locales/translated/tr_TR.json index dc4ade7b4..3dad71693 100644 --- a/plugins/remote-desktop/locales/translated/tr_TR.json +++ b/plugins/remote-desktop/locales/translated/tr_TR.json @@ -204,11 +204,75 @@ "fontName": "Yazı Tipi Adı", "fontSize": "Yazı Tipi Boyutu", "colorScheme": "Renk Şeması", - "backspaceKey": "Geri Silme Tuşu" - } + "backspaceKey": "Geri Silme Tuşu", + "colorDepth": "Renk Derinliği", + "resizeMethod": "Yeniden Boyutlandırma Yöntemi" + }, + "remoteDesktop": "Uzak Masaüstü", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Sanal Ağ", + "unencryptedShell": "Şifrelenmemiş Shell" }, "onboarding": { "feature_desktop": "Uzaktan masaüstü", "feature_desktop_desc": "SSH'de olduğu gibi, RDP, VNC ve Telnet oturumlarını da bir sekmede açın." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Renk Derinliği" + }, + "resizeMethod": { + "label": "Yeniden Boyutlandırma Yöntemi" + }, + "resizeDisplayUpdate": "Ekran Güncellemesi", + "resizeReconnect": "Yeniden bağlan", + "inherit": "Miras almak", + "on": "Açık", + "off": "Kapalı", + "forceLossless": { + "label": "Kayıpsız Zorla", + "description": "Kayıpsız görüntü kodlamasını zorla (daha yüksek kalite, daha fazla bant genişliği)" + }, + "enableWallpaper": { + "label": "Duvar Kağıdı", + "description": "Masaüstü duvar kağıdını göster (devre dışı bırakmak performansı artırır)" + }, + "enableFontSmoothing": { + "label": "Yazı Tipi Yumuşatma", + "description": "ClearType yazı tipi işlemeyi etkinleştir" + }, + "enableDesktopComposition": { + "label": "Masaüstü Birleştirme", + "description": "Aero cam efektlerini etkinleştir" + }, + "disableAudio": { + "label": "Sesi Devre Dışı Bırak", + "description": "Uzak oturumdaki tüm sesleri kapat" + }, + "enablePrinting": { + "label": "Yazdırmayı Etkinleştir", + "description": "Yerel yazıcıları uzak oturuma yönlendir" + }, + "enableDrive": { + "label": "Sürücü Yönlendirmesini Etkinleştir", + "description": "Yerel bir klasörü uzak oturumda sürücü olarak eşleştir" + }, + "disableCopy": { + "label": "Kopyalamayı Devre Dışı Bırak", + "description": "Uzak oturumdan metin kopyalamayı engelle" + }, + "disablePaste": { + "label": "Yapıştırmayı Devre Dışı Bırak", + "description": "Uzak oturuma metin yapıştırmayı engelle" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL'si" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/uk_UA.json b/plugins/remote-desktop/locales/translated/uk_UA.json index 1f5c68279..d81173337 100644 --- a/plugins/remote-desktop/locales/translated/uk_UA.json +++ b/plugins/remote-desktop/locales/translated/uk_UA.json @@ -204,11 +204,75 @@ "fontName": "Назва шрифту", "fontSize": "Розмір шрифту", "colorScheme": "Колірна схема", - "backspaceKey": "Клавіша Backspace" - } + "backspaceKey": "Клавіша Backspace", + "colorDepth": "Глибина кольору", + "resizeMethod": "Метод зміни розміру" + }, + "remoteDesktop": "Віддалений робочий стіл", + "tabRdp": "ПРП", + "tabVnc": "VNC", + "tabTelnet": "Телнет", + "virtualNetwork": "Віртуальна мережа", + "unencryptedShell": "Незашифрована оболонка" }, "onboarding": { "feature_desktop": "Віддалений робочий стіл", "feature_desktop_desc": "Відкривати сеанси RDP, VNC та Telnet у вкладці, так само, як SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Глибина кольору" + }, + "resizeMethod": { + "label": "Метод зміни розміру" + }, + "resizeDisplayUpdate": "Оновлення дисплея", + "resizeReconnect": "Знову підключитися", + "inherit": "Успадкувати", + "on": "Увімкнено", + "off": "Вимкнено", + "forceLossless": { + "label": "Примусово без втрат", + "description": "Примусове кодування зображень без втрат (вища якість, більша пропускна здатність)" + }, + "enableWallpaper": { + "label": "Шпалери", + "description": "Показувати шпалери робочого столу (вимикання покращує продуктивність)" + }, + "enableFontSmoothing": { + "label": "Згладжування шрифтів", + "description": "Увімкнути рендеринг шрифтів ClearType" + }, + "enableDesktopComposition": { + "label": "Композиція робочого столу", + "description": "Увімкнути ефекти Aero Glass" + }, + "disableAudio": { + "label": "Вимкнути аудіо", + "description": "Вимкнути весь звук під час віддаленого сеансу" + }, + "enablePrinting": { + "label": "Увімкнути друк", + "description": "Перенаправити локальні принтери на віддалений сеанс" + }, + "enableDrive": { + "label": "Увімкнути перенаправлення диска", + "description": "Призначення локальної папки як диска у віддаленому сеансі" + }, + "disableCopy": { + "label": "Вимкнути копіювання", + "description": "Запобігти копіюванню тексту з віддаленого сеансу" + }, + "disablePaste": { + "label": "Вимкнути вставку", + "description": "Запобігти вставці тексту у віддалений сеанс" + } + }, + "admin": { + "guacdUrl": { + "label": "URL-адреса GUACD" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/vi_VN.json b/plugins/remote-desktop/locales/translated/vi_VN.json index c7c5a87ab..8a443127e 100644 --- a/plugins/remote-desktop/locales/translated/vi_VN.json +++ b/plugins/remote-desktop/locales/translated/vi_VN.json @@ -204,11 +204,75 @@ "fontName": "Tên phông chữ", "fontSize": "Cỡ chữ", "colorScheme": "Bảng màu", - "backspaceKey": "Phím Backspace" - } + "backspaceKey": "Phím Backspace", + "colorDepth": "Độ sâu màu", + "resizeMethod": "Phương thức thay đổi kích thước" + }, + "remoteDesktop": "Máy tính từ xa", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "Mạng ảo", + "unencryptedShell": "Shell không mã hóa" }, "onboarding": { "feature_desktop": "Máy tính từ xa", "feature_desktop_desc": "Mở các phiên RDP, VNC và Telnet trong cùng một tab, tương tự như SSH." + }, + "settings": { + "user": { + "colorDepth": { + "label": "Độ sâu màu" + }, + "resizeMethod": { + "label": "Phương thức thay đổi kích thước" + }, + "resizeDisplayUpdate": "Cập nhật hiển thị", + "resizeReconnect": "Kết nối lại", + "inherit": "Kế thừa", + "on": "TRÊN", + "off": "Tắt", + "forceLossless": { + "label": "Ép buộc Lossless", + "description": "Ép buộc mã hóa ảnh không mất dữ liệu (chất lượng cao hơn, tốn băng thông)" + }, + "enableWallpaper": { + "label": "Hình nền", + "description": "Hiển thị hình nền desktop (tắt để cải thiện hiệu suất)" + }, + "enableFontSmoothing": { + "label": "Làm mịn phông chữ", + "description": "Bật kết xuất phông chữ ClearType" + }, + "enableDesktopComposition": { + "label": "Tổ hợp màn hình nền", + "description": "Bật hiệu ứng kính Aero" + }, + "disableAudio": { + "label": "Tắt âm thanh", + "description": "Tắt tất cả âm thanh từ phiên từ xa" + }, + "enablePrinting": { + "label": "Bật in", + "description": "Chuyển hướng máy in cục bộ đến phiên từ xa" + }, + "enableDrive": { + "label": "Bật chuyển hướng ổ đĩa", + "description": "Ánh xạ thư mục cục bộ thành ổ đĩa trong phiên từ xa" + }, + "disableCopy": { + "label": "Tắt sao chép", + "description": "Ngăn sao chép văn bản từ phiên từ xa" + }, + "disablePaste": { + "label": "Tắt dán", + "description": "Ngăn dán văn bản vào phiên từ xa" + } + }, + "admin": { + "guacdUrl": { + "label": "URL guacd" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/zh_CN.json b/plugins/remote-desktop/locales/translated/zh_CN.json index 8c0bfff1f..71fef9727 100644 --- a/plugins/remote-desktop/locales/translated/zh_CN.json +++ b/plugins/remote-desktop/locales/translated/zh_CN.json @@ -204,11 +204,75 @@ "fontName": "字体名称", "fontSize": "字体大小", "colorScheme": "配色方案", - "backspaceKey": "退格键" - } + "backspaceKey": "退格键", + "colorDepth": "颜色深度", + "resizeMethod": "调整大小方法" + }, + "remoteDesktop": "远程桌面", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "虚拟网络", + "unencryptedShell": "未加密 Shell" }, "onboarding": { "feature_desktop": "远程桌面", "feature_desktop_desc": "像 SSH 一样,在标签页中直接打开 RDP、VNC 和 Telnet 会话。" + }, + "settings": { + "user": { + "colorDepth": { + "label": "颜色深度" + }, + "resizeMethod": { + "label": "调整大小方法" + }, + "resizeDisplayUpdate": "显示更新", + "resizeReconnect": "重新连接", + "inherit": "继承", + "on": "开启", + "off": "关闭", + "forceLossless": { + "label": "强制无损", + "description": "强制无损图像编码(质量更高,占用更多带宽)" + }, + "enableWallpaper": { + "label": "壁纸", + "description": "显示桌面壁纸(禁用可提高性能)" + }, + "enableFontSmoothing": { + "label": "字体平滑", + "description": "启用 ClearType 字体渲染" + }, + "enableDesktopComposition": { + "label": "桌面合成", + "description": "启用 Aero 玻璃效果" + }, + "disableAudio": { + "label": "禁用音频", + "description": "将远程会话的所有音频静音" + }, + "enablePrinting": { + "label": "启用打印", + "description": "将本地打印机重定向到远程会话" + }, + "enableDrive": { + "label": "启用驱动器重定向", + "description": "将本地文件夹映射为远程会话中的驱动器" + }, + "disableCopy": { + "label": "禁用复制", + "description": "禁止从远程会话复制文本" + }, + "disablePaste": { + "label": "禁用粘贴", + "description": "禁止粘贴文本到远程会话" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/locales/translated/zh_TW.json b/plugins/remote-desktop/locales/translated/zh_TW.json index c42952320..519e1e6cd 100644 --- a/plugins/remote-desktop/locales/translated/zh_TW.json +++ b/plugins/remote-desktop/locales/translated/zh_TW.json @@ -204,11 +204,75 @@ "fontName": "字型名稱", "fontSize": "字型大小", "colorScheme": "色彩配置", - "backspaceKey": "退格鍵" - } + "backspaceKey": "退格鍵", + "colorDepth": "色彩深度", + "resizeMethod": "調整尺寸方法" + }, + "remoteDesktop": "遠端桌面", + "tabRdp": "RDP", + "tabVnc": "VNC", + "tabTelnet": "Telnet", + "virtualNetwork": "虛擬網路", + "unencryptedShell": "未加密殼層" }, "onboarding": { "feature_desktop": "遠端桌面", "feature_desktop_desc": "像 SSH 一樣,在同一個標籤頁中開啟 RDP、VNC 和 Telnet 會話。" + }, + "settings": { + "user": { + "colorDepth": { + "label": "色彩深度" + }, + "resizeMethod": { + "label": "調整尺寸方法" + }, + "resizeDisplayUpdate": "顯示更新", + "resizeReconnect": "重新連接", + "inherit": "繼承", + "on": "在", + "off": "離開", + "forceLossless": { + "label": "強制無損", + "description": "強制使用無損影像編碼(品質較高,頻寬需求較大)" + }, + "enableWallpaper": { + "label": "桌面背景", + "description": "顯示桌面背景(停用可提升效能)" + }, + "enableFontSmoothing": { + "label": "字型平滑處理", + "description": "啟用 ClearType 字型轉譯" + }, + "enableDesktopComposition": { + "label": "桌面合成", + "description": "啟用 Aero 玻璃效果" + }, + "disableAudio": { + "label": "停用音訊", + "description": "將遠端工作階段的音訊全部靜音" + }, + "enablePrinting": { + "label": "啟用列印功能", + "description": "將本機印表機重新導向至遠端工作階段" + }, + "enableDrive": { + "label": "啟用磁碟機重新導向", + "description": "在遠端工作階段中將本機資料夾對應為磁碟機" + }, + "disableCopy": { + "label": "停用複製", + "description": "防止從遠端工作階段複製文字" + }, + "disablePaste": { + "label": "停用貼上", + "description": "防止將文字貼上至遠端工作階段" + } + }, + "admin": { + "guacdUrl": { + "label": "guacd URL" + } + } } } diff --git a/plugins/remote-desktop/manifest.json b/plugins/remote-desktop/manifest.json index 119307b36..1792c9d01 100644 --- a/plugins/remote-desktop/manifest.json +++ b/plugins/remote-desktop/manifest.json @@ -16,15 +16,33 @@ }, "capabilities": [ "hosts:read", + "credentials:read", "credentials:use", "ssh:connect", "network:serve", - "audit:read", + "desktop:window", "ui:surface" ], + "optionalDependencies": { + "session-recording": "^1.0.0", + "tunnels": "^1.0.0" + }, + "provides": [ + { + "service": "sessions.live", + "version": "1.0.0", + "permission": "remote-desktop.sessions", + "names": ["rdp", "vnc", "telnet"] + } + ], "requires": [ { "service": "recordings.writer", + "versionRange": "^1.1.0", + "optional": true + }, + { + "service": "tunnels.access", "versionRange": "^1.0.0", "optional": true } @@ -50,30 +68,369 @@ "openFrom": ["host-context-menu", "palette"] } ], - "hostCapability": [ - { - "key": "enableRdp", - "labelKey": "hosts.tabRdp", - "editorTab": "rdp" - }, - { - "key": "enableVnc", - "labelKey": "hosts.tabVnc", - "editorTab": "vnc" - }, - { - "key": "enableTelnet", - "labelKey": "hosts.tabTelnet", - "editorTab": "telnet" - } - ], "actionSlots": [ { "id": "remote-desktop.toolbar", "accepts": ["button"] } ], - "guest": true + "guest": true, + "permissions": [ + { + "name": "sessions", + "titleKey": "permissions.sessions.title", + "descriptionKey": "permissions.sessions.description", + "defaultRoles": ["admin", "user"] + } + ], + "settings": { + "admin": [ + { + "key": "enabled", + "type": "boolean", + "labelKey": "settings.admin.enabled.label", + "descriptionKey": "settings.admin.enabled.description", + "default": true + }, + { + "key": "guacdUrl", + "type": "string", + "labelKey": "settings.admin.guacdUrl.label", + "descriptionKey": "settings.admin.guacdUrl.description", + "placeholderKey": "settings.admin.guacdUrl.placeholder", + "requires": "enabled", + "default": "" + } + ], + "user": [ + { + "key": "colorDepth", + "type": "select", + "labelKey": "settings.user.colorDepth.label", + "descriptionKey": "settings.user.colorDepth.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "16", + "labelKey": "settings.user.colorDepth16" + }, + { + "value": "24", + "labelKey": "settings.user.colorDepth24" + }, + { + "value": "32", + "labelKey": "settings.user.colorDepth32" + } + ], + "default": "inherit" + }, + { + "key": "resizeMethod", + "type": "select", + "labelKey": "settings.user.resizeMethod.label", + "descriptionKey": "settings.user.resizeMethod.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "display-update", + "labelKey": "settings.user.resizeDisplayUpdate" + }, + { + "value": "reconnect", + "labelKey": "settings.user.resizeReconnect" + } + ], + "default": "inherit" + }, + { + "key": "forceLossless", + "type": "select", + "labelKey": "settings.user.forceLossless.label", + "descriptionKey": "settings.user.forceLossless.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "enableWallpaper", + "type": "select", + "labelKey": "settings.user.enableWallpaper.label", + "descriptionKey": "settings.user.enableWallpaper.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "enableFontSmoothing", + "type": "select", + "labelKey": "settings.user.enableFontSmoothing.label", + "descriptionKey": "settings.user.enableFontSmoothing.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "enableDesktopComposition", + "type": "select", + "labelKey": "settings.user.enableDesktopComposition.label", + "descriptionKey": "settings.user.enableDesktopComposition.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "disableAudio", + "type": "select", + "labelKey": "settings.user.disableAudio.label", + "descriptionKey": "settings.user.disableAudio.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "enablePrinting", + "type": "select", + "labelKey": "settings.user.enablePrinting.label", + "descriptionKey": "settings.user.enablePrinting.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "enableDrive", + "type": "select", + "labelKey": "settings.user.enableDrive.label", + "descriptionKey": "settings.user.enableDrive.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "disableCopy", + "type": "select", + "labelKey": "settings.user.disableCopy.label", + "descriptionKey": "settings.user.disableCopy.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + }, + { + "key": "disablePaste", + "type": "select", + "labelKey": "settings.user.disablePaste.label", + "descriptionKey": "settings.user.disablePaste.description", + "group": "settings.user.group", + "options": [ + { + "value": "inherit", + "labelKey": "settings.user.inherit" + }, + { + "value": "on", + "labelKey": "settings.user.on" + }, + { + "value": "off", + "labelKey": "settings.user.off" + } + ], + "default": "inherit" + } + ], + "host": { + "fields": [ + { + "key": "enableRdp", + "type": "boolean", + "labelKey": "settings.host.enableRdp.label", + "default": false, + "hidden": true + }, + { + "key": "enableVnc", + "type": "boolean", + "labelKey": "settings.host.enableVnc.label", + "default": false, + "hidden": true + }, + { + "key": "enableTelnet", + "type": "boolean", + "labelKey": "settings.host.enableTelnet.label", + "default": false, + "hidden": true + }, + { + "key": "rdpPort", + "type": "number", + "labelKey": "settings.host.rdpPort.label", + "min": 1, + "max": 65535, + "default": 3389, + "hidden": true + }, + { + "key": "vncPort", + "type": "number", + "labelKey": "settings.host.vncPort.label", + "min": 1, + "max": 65535, + "default": 5900, + "hidden": true + }, + { + "key": "telnetPort", + "type": "number", + "labelKey": "settings.host.telnetPort.label", + "min": 1, + "max": 65535, + "default": 23, + "hidden": true + }, + { + "key": "rdpSecurity", + "type": "string", + "labelKey": "settings.host.rdpSecurity.label", + "default": "", + "hidden": true + }, + { + "key": "rdpIgnoreCert", + "type": "boolean", + "labelKey": "settings.host.rdpIgnoreCert.label", + "default": false, + "hidden": true + }, + { + "key": "guacamoleConfig", + "type": "json", + "labelKey": "settings.host.guacamoleConfig.label", + "default": {}, + "hidden": true + }, + { + "key": "enableToolbar", + "type": "boolean", + "labelKey": "settings.host.enableToolbar.label", + "descriptionKey": "settings.host.enableToolbar.description", + "default": true + } + ] + } + } }, "locales": "locales", "platforms": ["linux", "win32", "darwin"] diff --git a/plugins/remote-desktop/src/backend/guacamole-server.ts b/plugins/remote-desktop/src/backend/guacamole-server.ts index 8887c724c..4cf585dea 100644 --- a/plugins/remote-desktop/src/backend/guacamole-server.ts +++ b/plugins/remote-desktop/src/backend/guacamole-server.ts @@ -1,18 +1,20 @@ import GuacamoleLite from "guacamole-lite"; -import { guacLogger } from "../../../../src/backend/utils/logger.js"; -import { - GuacamoleTokenService, - type GuacamoleRecordingMetadata, -} from "./token-service.js"; -import { getCurrentSettingValue } from "../../../../src/backend/database/repositories/factory.js"; -import { resolveGuacdOptions } from "../../../../src/backend/utils/guacd-config.js"; import fs from "fs"; import path from "path"; - -const tokenService = GuacamoleTokenService.getInstance(); +import type { IncomingMessage } from "http"; +import type { Duplex } from "stream"; +import type { + GuacamoleRecordingMetadata, + GuacamoleTokenService, + TermixGuacMeta, +} from "./token-service.js"; +import type { GuacdOptions } from "./guacd-config.js"; +import type { RemoteSessions } from "./sessions.js"; +import { errorMessage, type RemoteDesktopLogger } from "./log.js"; /** The recordings.writer service, when the session-recording plugin provides it. */ export interface RecordingsWriter { + enabledFor: (hostId: number) => Promise; createFinished: (input: { hostId: number; userId: string; @@ -25,147 +27,38 @@ export interface RecordingsWriter { }) => Promise<{ id: number }>; } -let pluginRecordings: RecordingsWriter | null = null; - -export function setPluginRecordings(recordings: RecordingsWriter | null): void { - pluginRecordings = recordings; -} - -function readGuacdOptions(): { host: string; port: number } { - let dbUrl: string | undefined; - try { - dbUrl = getCurrentSettingValue("guac_url") ?? undefined; - } catch { - // DB not available yet, use env var defaults - } - return resolveGuacdOptions(dbUrl); -} - const DATA_DIR = process.env.DATA_DIR || "./db/data"; -const GUACAMOLE_RECORDINGS_DIR = - process.env.GUACD_RECORDING_BACKEND_PATH || - path.join(DATA_DIR, "session_recordings", "guacamole"); -type GuacamoleClientConnection = { +/** Where the backend reads guacd's recordings from. */ +export function recordingsDir(env: NodeJS.ProcessEnv = process.env): string { + return ( + env.GUACD_RECORDING_BACKEND_PATH || + path.join(DATA_DIR, "session_recordings", "guacamole") + ); +} + +type ClientConnection = { guacamoleConnectionId?: string; connectionSettings?: { connection?: { type?: string; join?: string; readOnly?: boolean }; recording?: GuacamoleRecordingMetadata; - termixMeta?: { - termixConnectId: string; - hostId: number; - ownerUserId: string; - protocol: string; - }; + termixMeta?: TermixGuacMeta; }; + close?: () => void; }; -export interface GuacSessionInfo { - guacamoleConnectionId: string; - hostId: number; - ownerUserId: string; - protocol: string; - openedAt: number; -} - -// Keyed by termixConnectId (routes.ts's correlation id), populated once the -// primary connection's guacd handshake completes. -const guacSessionByConnectId = new Map(); -// Keyed by guacd's own guacamoleConnectionId, for join-time lookups. -const guacSessionByGuacamoleId = new Map(); -export function getGuacSessionByConnectId( - connectId: string, - userId: string, -): GuacSessionInfo | null { - const info = guacSessionByConnectId.get(connectId); - return info?.ownerUserId === userId ? info : null; -} - -export function getGuacSessionInfo( - guacamoleConnectionId: string, -): GuacSessionInfo | null { - return guacSessionByGuacamoleId.get(guacamoleConnectionId) ?? null; -} - -async function persistGuacamoleRecording( - clientConnection: GuacamoleClientConnection, -): Promise { - const recording = clientConnection.connectionSettings?.recording; - if (!recording) return; - - const resolvedPath = path.resolve(GUACAMOLE_RECORDINGS_DIR, recording.path); - const allowedBase = `${path.resolve(GUACAMOLE_RECORDINGS_DIR)}${path.sep}`; - if (!resolvedPath.startsWith(allowedBase)) return; - - // guacd may flush/rename the recording just after the websocket closes. - for ( - let attempt = 0; - attempt < 10 && !fs.existsSync(resolvedPath); - attempt++ - ) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - if (!fs.existsSync(resolvedPath)) { - const guacdPath = recording.guacdPath ?? GUACAMOLE_RECORDINGS_DIR; - guacLogger.warn("Guacamole recording file was not found", { - operation: "guac_recording_missing", - hostId: recording.hostId, - path: resolvedPath, - guacdPath, - hint: - "guacd writes the recording to guacdPath, the backend reads it from path. " + - "When guacd runs in its own container these must be the same volume — set " + - "GUACD_RECORDING_PATH to guacd's mount point and GUACD_RECORDING_BACKEND_PATH to this one.", - }); - return; - } - - if (!pluginRecordings) { - guacLogger.warn( - "Session Recording plugin is off; guacamole recording not saved to the log", - { operation: "guac_recording_no_writer", hostId: recording.hostId }, - ); - return; - } - - const endedAt = new Date(); - const startedAt = new Date(recording.startedAt); - await pluginRecordings.createFinished({ - hostId: recording.hostId, - userId: recording.userId, - startedAt: startedAt.toISOString(), - endedAt: endedAt.toISOString(), - duration: Math.max( - 0, - Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000), - ), - recordingPath: resolvedPath, - protocol: recording.protocol, - format: "guacamole", - }); -} - -// noServer: core owns the listening socket. guacamole-lite passes this object -// straight to ws, and exposes the WebSocketServer it builds, so the plugin can -// hand it an upgrade from /plugin-ws/remote-desktop/display. There is no -// guacamole port any more. -const websocketOptions = { - noServer: true, -}; - -const clientOptions = { +const clientOptions = ( + tokens: GuacamoleTokenService, + log: RemoteDesktopLogger, +) => ({ crypt: { cypher: "AES-256-CBC", - key: tokenService.getEncryptionKey(), + key: tokens.getEncryptionKey(), }, log: { level: "ERRORS", - stdLog: (...args: unknown[]) => { - guacLogger.info(args.join(" ")); - }, - errorLog: (...args: unknown[]) => { - guacLogger.error(args.join(" ")); - }, + stdLog: (...args: unknown[]) => log.info(args.join(" ")), + errorLog: (...args: unknown[]) => log.error(args.join(" ")), }, allowedUnencryptedConnectionSettings: { rdp: ["width", "height", "dpi"], @@ -202,150 +95,224 @@ const clientOptions = { "terminal-type": "xterm-256color", }, }, -}; +}); -const _origConsoleLog = console.log; -console.log = (...args: unknown[]) => { - const msg = args[0]; - if (typeof msg === "string" && msg.startsWith("New client connection")) - return; - _origConsoleLog(...args); -}; +export interface GuacamoleServerDeps { + log: RemoteDesktopLogger; + tokens: GuacamoleTokenService; + sessions: RemoteSessions; + guacd: () => GuacdOptions; + recordings: () => RecordingsWriter | null; + /** A socket event has no request behind it, so the row is written as the session's user. */ + asUser: (userId: string, fn: () => Promise) => Promise; + /** Called when a primary session opens; returns what to release on close. */ + onOpen?: (hostId: number) => () => void; +} -function createGuacServer(): GuacamoleLite { - const guacdOptions = readGuacdOptions(); - const server = new GuacamoleLite( - websocketOptions, - guacdOptions, - clientOptions, - ); +export interface GuacamoleServer { + handleUpgrade: ( + request: IncomingMessage, + socket: Duplex, + head: Buffer, + ) => void; + /** Rebuilds the server so a changed guacd address takes effect. */ + restart: () => void; + /** Closes a session and everyone watching it. */ + endSession: (guacamoleConnectionId: string) => boolean; + close: () => void; +} - server.on("open", (clientConnection: GuacamoleClientConnection) => { - guacLogger.info("Guacamole connection opened", { - operation: "guac_connection_open", - type: clientConnection.connectionSettings?.connection?.type, - }); +/** + * guacamole-lite in noServer mode: core owns the listening socket and hands + * upgrades from /plugin-ws/remote-desktop/display to its WebSocketServer. + */ +export function createGuacamoleServer( + deps: GuacamoleServerDeps, +): GuacamoleServer { + const { log, sessions } = deps; + let server: GuacamoleLite | null = null; - const termixMeta = clientConnection.connectionSettings?.termixMeta; - const guacamoleConnectionId = clientConnection.guacamoleConnectionId; - const isJoin = !!clientConnection.connectionSettings?.connection?.join; + const persistRecording = async (connection: ClientConnection) => { + const recording = connection.connectionSettings?.recording; + if (!recording) return; - if (!isJoin && termixMeta && guacamoleConnectionId) { - const info: GuacSessionInfo = { - guacamoleConnectionId, - hostId: termixMeta.hostId, - ownerUserId: termixMeta.ownerUserId, - protocol: termixMeta.protocol, - openedAt: Date.now(), - }; - guacSessionByConnectId.set(termixMeta.termixConnectId, info); - guacSessionByGuacamoleId.set(guacamoleConnectionId, info); + const dir = recordingsDir(); + const resolvedPath = path.resolve(dir, recording.path); + if (!resolvedPath.startsWith(`${path.resolve(dir)}${path.sep}`)) return; + + // guacd may flush or rename the recording just after the socket closes. + for ( + let attempt = 0; + attempt < 10 && !fs.existsSync(resolvedPath); + attempt++ + ) { + await new Promise((resolve) => setTimeout(resolve, 100)); } - }); - - server.on("close", (clientConnection: GuacamoleClientConnection) => { - guacLogger.info("Guacamole connection closed", { - operation: "guac_connection_close", - type: clientConnection.connectionSettings?.connection?.type, - }); - - const isJoin = !!clientConnection.connectionSettings?.connection?.join; - const termixMeta = clientConnection.connectionSettings?.termixMeta; - const guacamoleConnectionId = clientConnection.guacamoleConnectionId; - if (!isJoin && termixMeta && guacamoleConnectionId) { - guacSessionByConnectId.delete(termixMeta.termixConnectId); - guacSessionByGuacamoleId.delete(guacamoleConnectionId); + if (!fs.existsSync(resolvedPath)) { + log.warn("Guacamole recording file was not found", { + operation: "guac_recording_missing", + hostId: recording.hostId, + path: resolvedPath, + guacdPath: recording.guacdPath ?? dir, + hint: + "guacd writes the recording to guacdPath, the backend reads it from path. " + + "When guacd runs in its own container these must be the same volume: set " + + "GUACD_RECORDING_PATH to guacd's mount point and GUACD_RECORDING_BACKEND_PATH to this one.", + }); + return; } - persistGuacamoleRecording(clientConnection).catch((error) => { - guacLogger.error("Failed to persist Guacamole recording", error, { - operation: "guac_recording_persist_error", + const writer = await deps.asUser(recording.userId, async () => + deps.recordings(), + ); + if (!writer) { + log.warn( + "Session Recording is off; the recording was kept on disk only", + { + operation: "guac_recording_no_writer", + hostId: recording.hostId, + }, + ); + return; + } + + const endedAt = new Date(); + const startedAt = new Date(recording.startedAt); + await deps.asUser(recording.userId, () => + writer.createFinished({ + hostId: recording.hostId, + userId: recording.userId, + startedAt: startedAt.toISOString(), + endedAt: endedAt.toISOString(), + duration: Math.max( + 0, + Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000), + ), + recordingPath: resolvedPath, + protocol: recording.protocol, + format: "guacamole", + }), + ); + }; + + const build = (): GuacamoleLite => { + // guacamole-lite fills in port 8080 unless it sees a server key, and ws + // refuses a port next to noServer, so the port is cleared explicitly. + const guacamole = new GuacamoleLite( + { noServer: true, port: undefined }, + deps.guacd(), + clientOptions(deps.tokens, log), + ); + // It also installs its own SIGTERM and SIGINT handlers. Core decides + // when the process stops and closes this through deactivate instead. + const handlers = guacamole as unknown as { + sigTermHandler?: () => void; + sigIntHandler?: () => void; + }; + if (handlers.sigTermHandler) { + process.off("SIGTERM", handlers.sigTermHandler); + } + if (handlers.sigIntHandler) { + process.off("SIGINT", handlers.sigIntHandler); + } + + guacamole.on("open", (connection: ClientConnection) => { + const meta = connection.connectionSettings?.termixMeta; + const guacamoleConnectionId = connection.guacamoleConnectionId; + const isJoin = !!connection.connectionSettings?.connection?.join; + if (isJoin || !meta || !guacamoleConnectionId) return; + const release = deps.onOpen?.(meta.hostId); + sessions.opened(meta, guacamoleConnectionId, release ? [release] : []); + }); + + guacamole.on("close", (connection: ClientConnection) => { + const meta = connection.connectionSettings?.termixMeta; + const isJoin = !!connection.connectionSettings?.connection?.join; + if (!isJoin && meta) sessions.closed(meta.termixConnectId); + persistRecording(connection).catch((error) => { + log.error("Failed to persist Guacamole recording", { + operation: "guac_recording_persist_error", + error: errorMessage(error), + }); }); }); - }); - server.on( - "error", - (clientConnection: GuacamoleClientConnection, error: Error) => { - guacLogger.error("Guacamole connection error", error, { + guacamole.on("error", (connection: ClientConnection, error: Error) => { + log.error("Guacamole connection error", { operation: "guac_connection_error", - type: clientConnection.connectionSettings?.connection?.type, + type: connection.connectionSettings?.connection?.type, + error: errorMessage(error), + }); + }); + + return guacamole; + }; + + const closeServer = () => { + if (!server) return; + try { + server.close(); + } catch (error) { + log.error("Error closing the Guacamole server", { + error: errorMessage(error), + }); + } + server = null; + }; + + server = build(); + + return { + handleUpgrade(request, socket, head) { + const wss = ( + server as unknown as { + webSocketServer?: { + handleUpgrade: ( + request: IncomingMessage, + socket: Duplex, + head: Buffer, + done: (ws: unknown) => void, + ) => void; + emit: (event: string, ...args: unknown[]) => void; + }; + } | null + )?.webSocketServer; + if (!wss) { + socket.destroy(); + return; + } + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit("connection", ws, request); }); }, - ); - return server; + restart() { + if (!server) return; + closeServer(); + server = build(); + }, + + endSession(guacamoleConnectionId) { + const active = ( + server as unknown as { + activeConnections?: Map; + } | null + )?.activeConnections; + if (!active) return false; + let closed = false; + for (const connection of [...active.values()]) { + const join = connection.connectionSettings?.connection?.join; + if ( + connection.guacamoleConnectionId === guacamoleConnectionId || + join === guacamoleConnectionId + ) { + connection.close?.(); + closed = true; + } + } + return closed; + }, + + close: closeServer, + }; } - -let guacServer: GuacamoleLite | null = null; - -/** - * Builds the guacamole-lite server. Called from this plugin's activate(). - * Nothing binds a port: handleGuacamoleUpgrade below feeds it connections. - */ -export async function startGuacamoleService(): Promise { - guacServer = createGuacServer(); -} - -/** - * Hands one upgrade to guacamole-lite's own WebSocketServer. - * - * guacamole-lite constructs that server internally and has no "attach to this - * http server" option, so giving it the upgrade is the only way in. Core has - * already authenticated the request by the time this runs. - */ -export function handleGuacamoleUpgrade( - request: import("http").IncomingMessage, - socket: import("stream").Duplex, - head: Buffer, -): void { - const server = guacServer as unknown as { - webSocketServer?: { - handleUpgrade: ( - request: import("http").IncomingMessage, - socket: import("stream").Duplex, - head: Buffer, - done: (ws: unknown) => void, - ) => void; - emit: (event: string, ...args: unknown[]) => void; - }; - } | null; - - const wss = server?.webSocketServer; - if (!wss) { - socket.destroy(); - return; - } - - wss.handleUpgrade(request, socket, head, (ws) => { - wss.emit("connection", ws, request); - }); -} - -/** - * Stops the guacamole-lite WebSocket server. Called from this plugin's - * deactivate(). - */ -export async function stopGuacamoleService(): Promise { - if (!guacServer) return; - try { - guacServer.close(); - } catch (err) { - guacLogger.error("Error closing guac server during shutdown", err as Error); - } - guacServer = null; -} - -export async function restartGuacServer(): Promise { - // A no-op while the plugin is disabled: creating a new server here would - // silently resurrect the WS server behind deactivate()'s back. - if (!guacServer) return; - try { - guacServer.close(); - } catch (err) { - guacLogger.error("Error closing guac server during restart", err as Error); - } - guacServer = createGuacServer(); -} - -export { guacServer, tokenService }; diff --git a/src/backend/utils/guacd-config.ts b/plugins/remote-desktop/src/backend/guacd-config.ts similarity index 100% rename from src/backend/utils/guacd-config.ts rename to plugins/remote-desktop/src/backend/guacd-config.ts diff --git a/plugins/remote-desktop/src/backend/host-settings.ts b/plugins/remote-desktop/src/backend/host-settings.ts new file mode 100644 index 000000000..b7d51e61c --- /dev/null +++ b/plugins/remote-desktop/src/backend/host-settings.ts @@ -0,0 +1,187 @@ +import type { PluginContext } from "@termix/plugin-sdk/backend"; + +export type RemoteProtocol = "rdp" | "vnc" | "telnet"; + +export const PLUGIN_ID = "remote-desktop"; + +export interface RemoteDesktopHostSettings { + enableRdp: boolean; + enableVnc: boolean; + enableTelnet: boolean; + rdpPort: number; + vncPort: number; + telnetPort: number; + rdpSecurity: string; + rdpIgnoreCert: boolean; + guacamoleConfig: Record; + enableToolbar: boolean; +} + +export const ENABLE_KEY: Record< + RemoteProtocol, + keyof RemoteDesktopHostSettings +> = { rdp: "enableRdp", vnc: "enableVnc", telnet: "enableTelnet" }; + +export const PORT_KEY: Record = + { rdp: "rdpPort", vnc: "vncPort", telnet: "telnetPort" }; + +export const DEFAULT_PORT: Record = { + rdp: 3389, + vnc: 5900, + telnet: 23, +}; + +export function isRemoteProtocol(value: unknown): value is RemoteProtocol { + return value === "rdp" || value === "vnc" || value === "telnet"; +} + +function asPort(value: unknown): number | undefined { + const port = typeof value === "string" ? Number(value) : value; + return typeof port === "number" && + Number.isInteger(port) && + port > 0 && + port <= 65535 + ? port + : undefined; +} + +function asObject(value: unknown): Record | undefined { + let parsed = value; + if (typeof value === "string") { + try { + parsed = JSON.parse(value); + } catch { + return undefined; + } + } + return parsed && typeof parsed === "object" && !Array.isArray(parsed) + ? (parsed as Record) + : undefined; +} + +function asBool(value: unknown): boolean | undefined { + if (typeof value === "boolean") return value; + if (value === 1 || value === "1" || value === "true") return true; + if (value === 0 || value === "0" || value === "false") return false; + return undefined; +} + +export async function readHostSettings( + ctx: PluginContext, + hostId: number, +): Promise { + const values = await ctx.settings.getAll("host", hostId); + return { + enableRdp: values.enableRdp === true, + enableVnc: values.enableVnc === true, + enableTelnet: values.enableTelnet === true, + rdpPort: asPort(values.rdpPort) ?? DEFAULT_PORT.rdp, + vncPort: asPort(values.vncPort) ?? DEFAULT_PORT.vnc, + telnetPort: asPort(values.telnetPort) ?? DEFAULT_PORT.telnet, + rdpSecurity: + typeof values.rdpSecurity === "string" ? values.rdpSecurity : "", + rdpIgnoreCert: values.rdpIgnoreCert === true, + guacamoleConfig: asObject(values.guacamoleConfig) ?? {}, + enableToolbar: values.enableToolbar !== false, + }; +} + +/** + * Turns an imported host row into this plugin's host settings. + * + * A Termix export carries them under pluginSettings["remote-desktop"]. Older + * exports, other tools and a plugin creating a host (proxmox) use the flat + * fields the host row had before 2.9.0, including the connectionType-only + * shape of hosts from before the per-protocol switches existed. + */ +export function normalizeImportedHost( + raw: Record, +): Record | null { + const nested = + asObject(asObject(raw.pluginSettings)?.[PLUGIN_ID]) ?? + ({} as Record); + const pick = (key: string, legacy?: string) => + nested[key] !== undefined + ? nested[key] + : raw[key] !== undefined + ? raw[key] + : legacy !== undefined + ? raw[legacy] + : undefined; + + const out: Record = {}; + for (const protocol of ["rdp", "vnc", "telnet"] as const) { + const key = ENABLE_KEY[protocol]; + const flag = asBool(pick(key)); + if (flag !== undefined) out[key] = flag; + const port = asPort(pick(PORT_KEY[protocol])); + if (port !== undefined) out[PORT_KEY[protocol]] = port; + } + + const anyFlag = ["enableRdp", "enableVnc", "enableTelnet"].some( + (key) => out[key] === true, + ); + if (!anyFlag && isRemoteProtocol(raw.connectionType)) { + out[ENABLE_KEY[raw.connectionType]] = true; + const port = asPort(raw.port); + if (port !== undefined && out[PORT_KEY[raw.connectionType]] === undefined) { + out[PORT_KEY[raw.connectionType]] = port; + } + } + + const security = pick("rdpSecurity", "security"); + if (typeof security === "string" && security) out.rdpSecurity = security; + const ignoreCert = asBool(pick("rdpIgnoreCert", "ignoreCert")); + if (ignoreCert !== undefined) out.rdpIgnoreCert = ignoreCert; + const config = asObject(pick("guacamoleConfig")); + if (config && Object.keys(config).length > 0) out.guacamoleConfig = config; + const toolbar = asBool(pick("enableToolbar", "enableTerminalToolbar")); + if (toolbar !== undefined) out.enableToolbar = toolbar; + + return Object.keys(out).length > 0 ? out : null; +} + +/** User setting key to the guacd parameter it sets. */ +const USER_DEFAULT_PARAMS: Record = { + colorDepth: "color-depth", + resizeMethod: "resize-method", + forceLossless: "force-lossless", + enableWallpaper: "enable-wallpaper", + enableFontSmoothing: "enable-font-smoothing", + enableDesktopComposition: "enable-desktop-composition", + disableAudio: "disable-audio", + enablePrinting: "enable-printing", + enableDrive: "enable-drive", + disableCopy: "disable-copy", + disablePaste: "disable-paste", +}; + +/** + * The user's RDP defaults as guacd parameters. "inherit" leaves a parameter + * to the host and guacd; on and off become booleans. + */ +export function userDefaultParams( + values: Record, +): Record { + const params: Record = {}; + for (const [key, param] of Object.entries(USER_DEFAULT_PARAMS)) { + const value = values[key]; + if (value === undefined || value === null || value === "inherit") continue; + if (key === "colorDepth") { + const depth = Number(value); + if (Number.isInteger(depth) && depth > 0) params[param] = depth; + } else if (key === "resizeMethod") { + if (typeof value === "string" && value) params[param] = value; + } else if (value === "on" || value === "off") { + params[param] = value === "on"; + } + } + return params; +} + +export async function readUserDefaults( + ctx: PluginContext, + userId: string, +): Promise> { + return userDefaultParams(await ctx.settings.getAll("user", userId)); +} diff --git a/plugins/remote-desktop/src/backend/index.ts b/plugins/remote-desktop/src/backend/index.ts index cc7f1416e..ab62cd09c 100644 --- a/plugins/remote-desktop/src/backend/index.ts +++ b/plugins/remote-desktop/src/backend/index.ts @@ -1,63 +1,108 @@ import type { PluginContext } from "@termix/plugin-sdk/backend"; -import { setPluginSsh } from "./ssh.js"; +import { GuacamoleTokenService } from "./token-service.js"; +import { RemoteSessions } from "./sessions.js"; import { - getGuacSessionInfo, - handleGuacamoleUpgrade, - restartGuacServer, - setPluginRecordings, - startGuacamoleService, - stopGuacamoleService, - tokenService, + createGuacamoleServer, type RecordingsWriter, } from "./guacamole-server.js"; -import { startRemoteDesktopService } from "./routes.js"; +import { resolveGuacdOptions } from "./guacd-config.js"; +import { registerRoutes } from "./routes.js"; +import { createLiveSessions } from "./live-sessions.js"; +import { normalizeImportedHost } from "./host-settings.js"; +import { createLogger } from "./log.js"; export async function activate(ctx: PluginContext) { - setPluginSsh(ctx.ssh); - ctx.disposables.add(() => setPluginSsh(null)); + const log = createLogger(ctx.log); + const tokens = new GuacamoleTokenService(log); + const sessions = new RemoteSessions(); + ctx.disposables.add(() => sessions.clear()); - // Optional: without the session-recording plugin, a guacd recording is - // still made on disk but never gets a row, so it never shows in the list. - const recordings = ctx.services.get("recordings.writer"); - setPluginRecordings("createFinished" in recordings ? recordings : null); - ctx.disposables.add(() => setPluginRecordings(null)); + // The admin setting, unless GUACD_URL / GUACD_HOST / GUACD_PORT override it. + let guacdUrl = (await ctx.settings.get("guacdUrl")) ?? ""; + const guacd = () => resolveGuacdOptions(guacdUrl || undefined); + const enabled = async () => + (await ctx.settings.get("enabled")) !== false; - await startGuacamoleService(); + // Optional: without session-recording nothing is recorded. + const recordings = (): RecordingsWriter | null => { + const service = + ctx.services.get>("recordings.writer"); + return typeof service.createFinished === "function" && + typeof service.enabledFor === "function" + ? (service as RecordingsWriter) + : null; + }; - startRemoteDesktopService(ctx.http.router()); + // guacamole-lite prints every new connection to stdout. + const consoleLog = console.log; + console.log = (...args: unknown[]) => { + if ( + typeof args[0] === "string" && + args[0].startsWith("New client connection") + ) + return; + consoleLog(...args); + }; + ctx.disposables.add(() => { + console.log = consoleLog; + }); + + const server = createGuacamoleServer({ + log, + tokens, + sessions, + guacd, + recordings, + asUser: (userId, fn) => ctx.asUser(userId, fn), + onOpen: (hostId) => ctx.hosts.trackSession(hostId), + }); + ctx.disposables.add(() => server.close()); + + ctx.settings.onChange("guacdUrl", (value) => { + guacdUrl = typeof value === "string" ? value : ""; + server.restart(); + }); + + registerRoutes(ctx.http.router(), { + ctx, + log, + tokens, + sessions, + guacd, + enabled, + recordings, + }); // guacamole-lite owns its own WebSocketServer, so it takes the raw upgrade. - // - // Public because a Guacamole display authenticates with the encrypted, - // single-use connection token minted by POST /token, not with a session - // JWT. guacamole-lite decrypts and validates that token itself, and the - // route that mints one is authenticated normally. + // Public because a display authenticates with the encrypted, single-use + // token minted by an authenticated route, not with a session JWT. ctx.ws.upgrade( "/display", (request, socket, head) => - handleGuacamoleUpgrade( - request as Parameters[0], - socket as Parameters[1], + server.handleUpgrade( + request as Parameters[0], + socket as Parameters[1], head as Buffer, ), { public: true }, ); - // What collab, session-sharing and the admin guacd setting need from a - // running Guacamole server. Revoked automatically on deactivate, and - // hosts/guacamole-sessions.ts degrades safely while it is gone. - ctx.registry.provide("remote-desktop.sessions", { - restart: restartGuacServer, - createJoinToken: (guacamoleConnectionId: string, readOnly: boolean) => - tokenService.createJoinToken(guacamoleConnectionId, readOnly), - getSessionInfo: getGuacSessionInfo, - }); + for (const protocol of ["rdp", "vnc", "telnet"] as const) { + ctx.services.provide( + "sessions.live", + createLiveSessions(protocol, { + sessions, + tokens, + endSession: server.endSession, + }), + { name: protocol }, + ); + } - ctx.log.info( - "Remote Desktop mounted at /plugin-api/remote-desktop and /plugin-ws/remote-desktop/display", + ctx.registry.provide( + "remote-desktop.hostImportNormalizer", + normalizeImportedHost, ); } -export async function deactivate() { - await stopGuacamoleService(); -} +export async function deactivate() {} diff --git a/plugins/remote-desktop/src/backend/jump-tunnel.ts b/plugins/remote-desktop/src/backend/jump-tunnel.ts new file mode 100644 index 000000000..9e0377842 --- /dev/null +++ b/plugins/remote-desktop/src/backend/jump-tunnel.ts @@ -0,0 +1,145 @@ +import net from "net"; +import type { Client } from "ssh2"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { RemoteDesktopLogger } from "./log.js"; + +/** The tunnels plugin's service, as ctx.services.get("tunnels.access") returns it. */ +interface TunnelsAccess { + forward( + sourceHostId: number, + target: { targetHost: string; targetPort: number; bindHost?: string }, + options?: { name?: string; idleTimeoutMs?: number }, + ): Promise<{ + bindHost: string; + bindPort: number; + close: () => Promise; + }>; +} + +/** The tunnels plugin keeps these names for on-demand forwards. */ +const TUNNEL_NAME_PREFIX = "web:"; +/** A forward nothing is using closes itself after this long. */ +const FORWARD_IDLE_TIMEOUT_MS = 10 * 60 * 1000; + +export interface JumpTunnel { + port: number; + close: () => void; +} + +export interface JumpTunnelRequest { + jumpHosts: Array<{ hostId: number }>; + targetHost: string; + targetPort: number; + bindHost: string; + /** Names the forward, so each session gets one of its own. */ + connectId: string; +} + +/** + * A local port guacd can dial that reaches the target through the host's + * jump hosts. + * + * One hop is exactly what the tunnels plugin's forward does, so that is used + * when the plugin is running and the user may use it. A chain, or no tunnels + * plugin, goes through ctx.ssh.jumpChain with a listener of our own. Either + * way the caller closes it when the session ends. + */ +export async function openJumpTunnel( + ctx: PluginContext, + log: RemoteDesktopLogger, + request: JumpTunnelRequest, +): Promise { + if (request.jumpHosts.length === 1) { + const viaTunnels = await forwardThroughTunnels(ctx, log, request); + if (viaTunnels) return viaTunnels; + } + return forwardThroughChain(ctx, request); +} + +async function forwardThroughTunnels( + ctx: PluginContext, + log: RemoteDesktopLogger, + request: JumpTunnelRequest, +): Promise { + const tunnels = ctx.services.get>("tunnels.access"); + if (typeof tunnels.forward !== "function") return null; + const hopId = request.jumpHosts[0].hostId; + try { + const handle = await tunnels.forward( + hopId, + { + targetHost: request.targetHost, + targetPort: request.targetPort, + bindHost: request.bindHost, + }, + { + name: `${TUNNEL_NAME_PREFIX}${hopId}:rd-${request.connectId}`, + idleTimeoutMs: FORWARD_IDLE_TIMEOUT_MS, + }, + ); + return { + port: handle.bindPort, + close: () => void handle.close().catch(() => undefined), + }; + } catch (error) { + // A user without the tunnels permission still reaches their host. + log.warn("Tunnels forward failed, using a jump chain instead", { + operation: "guac_tunnels_forward", + hopId, + error: error instanceof Error ? error.message : String(error), + }); + return null; + } +} + +async function forwardThroughChain( + ctx: PluginContext, + request: JumpTunnelRequest, +): Promise { + // The chain dials the first hop through that hop's own SOCKS5 settings; + // the target host's proxy config does not apply to it. + const { client, dispose } = await ctx.ssh.jumpChain( + request.jumpHosts, + ); + + const sockets = new Set(); + const server = net.createServer((socket) => { + sockets.add(socket); + socket.once("close", () => sockets.delete(socket)); + client.forwardOut( + "127.0.0.1", + 0, + request.targetHost, + request.targetPort, + (error, stream) => { + if (error) { + socket.destroy(); + return; + } + socket.pipe(stream).pipe(socket); + }, + ); + }); + + let port: number; + try { + port = await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, request.bindHost, () => + resolve((server.address() as net.AddressInfo).port), + ); + }); + } catch (error) { + await dispose(); + throw error; + } + + return { + port, + close: () => { + for (const socket of sockets) socket.destroy(); + server.close(); + void dispose(); + }, + }; +} diff --git a/plugins/remote-desktop/src/backend/live-sessions.ts b/plugins/remote-desktop/src/backend/live-sessions.ts new file mode 100644 index 000000000..5f3189f58 --- /dev/null +++ b/plugins/remote-desktop/src/backend/live-sessions.ts @@ -0,0 +1,65 @@ +import type { GuacamoleTokenService } from "./token-service.js"; +import type { RemoteSessions } from "./sessions.js"; +import type { RemoteProtocol } from "./host-settings.js"; + +/** sessions.live v1, as session-sharing reads it (plugins/session-sharing/src/backend/live.ts). */ +export interface LiveSessionInfo { + id: string; + userId: string; + hostId: number; + hostName: string; + isConnected: boolean; + createdAt: number; + tabInstanceId: string | null; +} + +export interface RemoteLiveSessions { + getSession: (sessionId: string) => LiveSessionInfo | null; + ownerEndSession: (sessionId: string, reason: string) => void; + createViewerToken: (sessionId: string, readOnly: boolean) => string; +} + +/** + * One provider per protocol. A session id is guacd's own connection id, + * which is what a viewer's join token names. + */ +export function createLiveSessions( + protocol: RemoteProtocol, + deps: { + sessions: RemoteSessions; + tokens: GuacamoleTokenService; + endSession: (guacamoleConnectionId: string) => boolean; + }, +): RemoteLiveSessions { + const find = (sessionId: string) => { + const session = deps.sessions.byGuacamole(sessionId); + return session && session.protocol === protocol ? session : null; + }; + + return { + getSession(sessionId) { + const session = find(sessionId); + if (!session) return null; + return { + id: session.guacamoleConnectionId, + userId: session.ownerUserId, + hostId: session.hostId, + hostName: session.hostName, + isConnected: true, + createdAt: session.openedAt, + tabInstanceId: session.tabInstanceId, + }; + }, + + ownerEndSession(sessionId) { + if (find(sessionId)) deps.endSession(sessionId); + }, + + createViewerToken(sessionId, readOnly) { + if (!find(sessionId)) { + throw new Error(`No live ${protocol} session ${sessionId}`); + } + return deps.tokens.createJoinToken(sessionId, readOnly); + }, + }; +} diff --git a/plugins/remote-desktop/src/backend/log.ts b/plugins/remote-desktop/src/backend/log.ts new file mode 100644 index 000000000..010d0f660 --- /dev/null +++ b/plugins/remote-desktop/src/backend/log.ts @@ -0,0 +1,30 @@ +import type { PluginLogger } from "@termix/plugin-sdk/backend"; + +export type Meta = Record; + +export interface RemoteDesktopLogger { + info: (message: string, meta?: Meta) => void; + warn: (message: string, meta?: Meta) => void; + error: (message: string, meta?: Meta) => void; +} + +function withMeta(message: string, meta?: Meta): string { + return meta && Object.keys(meta).length > 0 + ? `${message} ${JSON.stringify(meta)}` + : message; +} + +/** ctx.log with the structured context the old guac logger carried. */ +export function createLogger(log: PluginLogger): RemoteDesktopLogger { + return { + info: (message, meta) => log.info(withMeta(message, meta)), + warn: (message, meta) => log.warn(withMeta(message, meta)), + error: (message, meta) => log.error(withMeta(message, meta)), + }; +} + +export function errorMessage(error: unknown, fallback = "Unknown error") { + if (error instanceof Error && error.message) return error.message; + if (typeof error === "string" && error) return error; + return fallback; +} diff --git a/plugins/remote-desktop/src/backend/rdp-settings.ts b/plugins/remote-desktop/src/backend/rdp-settings.ts index 305c153cb..63ada8e14 100644 --- a/plugins/remote-desktop/src/backend/rdp-settings.ts +++ b/plugins/remote-desktop/src/backend/rdp-settings.ts @@ -34,31 +34,3 @@ export function resolveRdpDomain( ? promptedDomain : storedDomain; } - -type SharedRdpAuthResolution = - | { source: "personal-override" } - | { source: "owner-shared"; authType: string } - | { source: "secretless" } - | { source: "required" } - | null; - -export function resolveRdpAuthTypeForConnect({ - storedAuthType, - credentialId, - sharedResolution, -}: { - storedAuthType?: string | null; - credentialId?: number | null; - sharedResolution?: SharedRdpAuthResolution; -}): string { - if (sharedResolution === undefined) { - return storedAuthType || (credentialId ? "credential" : "direct"); - } - if (sharedResolution?.source === "personal-override") return "credential"; - if (sharedResolution?.source === "owner-shared") { - return sharedResolution.authType; - } - return sharedResolution?.source === "secretless" && storedAuthType === "none" - ? "none" - : "direct"; -} diff --git a/plugins/remote-desktop/src/backend/routes.ts b/plugins/remote-desktop/src/backend/routes.ts index b865adddb..66a8dd036 100644 --- a/plugins/remote-desktop/src/backend/routes.ts +++ b/plugins/remote-desktop/src/backend/routes.ts @@ -1,826 +1,601 @@ -import { getErrorMessage } from "../../../../src/backend/utils/error-message.js"; -import express, { type Router } from "express"; -import { GuacamoleTokenService } from "./token-service.js"; +import crypto from "crypto"; +import net from "net"; +import path from "path"; +import type { Request, Response, Router } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { GuacamoleTokenService } from "./token-service.js"; +import type { GuacdOptions } from "./guacd-config.js"; +import type { RemoteSessions } from "./sessions.js"; +import type { RecordingsWriter } from "./guacamole-server.js"; +import { recordingsDir } from "./guacamole-server.js"; import { withRecordingSettings } from "./recording-settings.js"; import { withDriveSettings } from "./drive-settings.js"; -import { guacLogger } from "../../../../src/backend/utils/logger.js"; -import { PermissionManager } from "../../../../src/backend/utils/permission-manager.js"; -import { - resolveRecipientSharedHostAuthentication, - type RecipientSharedHostAuthResolution, -} from "../../../../src/backend/utils/shared-host-auth-resolver.js"; -import type { AuthOverrideProtocol } from "../../../../src/types/auth-protocols.js"; -import net from "net"; -import crypto from "crypto"; -import path from "path"; -import type { AuthenticatedRequest } from "../../../../src/types/index.js"; -import { - createCurrentHostResolutionRepository, - createCurrentSettingsRepository, -} from "../../../../src/backend/database/repositories/factory.js"; -import { resolveGuacdOptions } from "../../../../src/backend/utils/guacd-config.js"; -import type { Client } from "ssh2"; -import { pluginSsh } from "./ssh.js"; -import { getGuacSessionByConnectId } from "./guacamole-server.js"; -import { - logAudit, - getAuditUsername, - getRequestMeta, -} from "../../../../src/backend/utils/audit-logger.js"; import { resolveJumpTunnelEndpoint } from "./jump-tunnel-endpoint.js"; -import { - buildRdpSettings, - resolveRdpAuthTypeForConnect, - resolveRdpDomain, -} from "./rdp-settings.js"; +import { buildRdpSettings, resolveRdpDomain } from "./rdp-settings.js"; import { createMacosVncCompatibilityProxy } from "./macos-vnc-proxy.js"; +import { openJumpTunnel } from "./jump-tunnel.js"; +import { + DEFAULT_PORT, + ENABLE_KEY, + PORT_KEY, + isRemoteProtocol, + readHostSettings, + readUserDefaults, + type RemoteProtocol, +} from "./host-settings.js"; +import { errorMessage, type RemoteDesktopLogger } from "./log.js"; -const router = express.Router(); -const tokenService = GuacamoleTokenService.getInstance(); -const DATA_DIR = process.env.DATA_DIR || "./db/data"; +export interface RouteDeps { + ctx: PluginContext; + log: RemoteDesktopLogger; + tokens: GuacamoleTokenService; + sessions: RemoteSessions; + guacd: () => GuacdOptions; + enabled: () => Promise; + recordings: () => RecordingsWriter | null; +} -// Core authenticates every /plugin-api route before it reaches here, so the -// router no longer applies its own middleware. +function actor(ctx: PluginContext, res: Response): string | null { + const userId = ctx.currentActor(); + if (!userId) res.status(401).json({ error: "Authentication required" }); + return userId ?? null; +} -router.get("/connection/:connectId", (req: AuthenticatedRequest, res) => { - if (!req.userId) - return res.status(401).json({ error: "Authentication required" }); - const session = getGuacSessionByConnectId( - String(req.params.connectId), - req.userId, - ); - res.json({ guacamoleConnectionId: session?.guacamoleConnectionId ?? null }); -}); +function probeGuacd({ host, port }: GuacdOptions): Promise { + return new Promise((resolve) => { + const socket = new net.Socket(); + socket.setTimeout(3000); + const done = (ok: boolean) => { + socket.destroy(); + resolve(ok); + }; + socket.once("connect", () => done(true)); + socket.once("timeout", () => done(false)); + socket.once("error", () => done(false)); + socket.connect(port, host); + }); +} -/** - * @openapi - * /guacamole/token: - * post: - * summary: Generate an encrypted Guacamole connection token - * description: Creates an AES-256-CBC encrypted token for guacamole-lite with the given connection parameters - * tags: - * - Guacamole - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - type - * - hostname - * properties: - * type: - * type: string - * enum: [rdp, vnc, telnet] - * hostname: - * type: string - * port: - * type: integer - * username: - * type: string - * password: - * type: string - * domain: - * type: string - * responses: - * 200: - * description: Encrypted connection token - * content: - * application/json: - * schema: - * type: object - * properties: - * token: - * type: string - * 400: - * description: Invalid request - * 500: - * description: Server error - */ -router.post("/token", async (req, res) => { - try { - const { type, hostname, port, username, password, domain, ...rawOptions } = - req.body; - - // Strip "auto" sentinel values before forwarding to guacd - const options: Record = {}; - for (const [key, value] of Object.entries(rawOptions)) { - if (value !== "auto") options[key] = value; - } - - if (!type || !hostname) { - return res - .status(400) - .json({ error: "Missing required fields: type and hostname" }); - } - - if (!["rdp", "vnc", "telnet"].includes(type)) { - return res.status(400).json({ - error: "Invalid connection type. Must be rdp, vnc, or telnet", - }); - } - - let token: string; - - switch (type) { - case "rdp": - token = tokenService.createRdpToken( - hostname, - username || "", - password || "", - { - port: port || 3389, - domain, - ...options, - }, - ); - break; - case "vnc": - token = tokenService.createVncToken( - hostname, - username || undefined, - password, - { - port: port || 5900, - ...options, - }, - ); - break; - case "telnet": - token = tokenService.createTelnetToken(hostname, username, password, { - port: port || 23, - ...options, - }); - break; - default: - return res.status(400).json({ error: "Invalid connection type" }); - } - - res.json({ token }); - } catch (error) { - guacLogger.error("Failed to generate guacamole token", error, { - operation: "guac_token_error", - }); - res.status(500).json({ error: "Failed to generate connection token" }); +/** guacd reads these settings as given; the editor stores "auto" for its default. */ +function cleanGuacConfig(raw: Record): { + config: Record; + guacdOverrides: { guacdHost?: string; guacdPort?: number }; +} { + const config: Record = {}; + for (const [key, value] of Object.entries(raw)) { + if (value !== "auto") config[key] = value; } -}); + const guacdHost = + typeof config["guacd-hostname"] === "string" && config["guacd-hostname"] + ? (config["guacd-hostname"] as string) + : undefined; + const guacdPort = config["guacd-port"] + ? parseInt(String(config["guacd-port"]), 10) || undefined + : undefined; + delete config["guacd-hostname"]; + delete config["guacd-port"]; + if (config.dpi != null) { + const dpi = parseInt(String(config.dpi), 10); + config.dpi = Number.isFinite(dpi) && dpi > 0 ? dpi : undefined; + } + return { + config, + guacdOverrides: { + ...(guacdHost ? { guacdHost } : {}), + ...(guacdPort ? { guacdPort } : {}), + }, + }; +} -/** - * @openapi - * /guacamole/connect-host/{hostId}: - * post: - * summary: Generate Guacamole connection token from host configuration - * description: Fetches host configuration from database and generates a connection token for RDP/VNC/Telnet - * tags: - * - Guacamole - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: hostId - * required: true - * schema: - * type: integer - * description: Host ID to connect to - * requestBody: - * required: false - * content: - * application/json: - * schema: - * type: object - * properties: - * protocol: - * type: string - * enum: [rdp, vnc, telnet] - * description: Override the host's default connection type - * promptedUsername: - * type: string - * description: Username for this connection only, used when the host's RDP auth type is "none". Not persisted. - * promptedPassword: - * type: string - * description: Password for this connection only, used when the host's RDP auth type is "none". Not persisted. - * responses: - * 200: - * description: Connection token generated successfully - * content: - * application/json: - * schema: - * type: object - * properties: - * token: - * type: string - * description: Encrypted connection token - * guacamoleConnectionId: - * type: string - * nullable: true - * description: Null until the WebSocket handshake completes. Query /guacamole/connection/{connectId} for the session-sharing ID. - * termixConnectId: - * type: string - * description: Correlation ID for looking up this connection after the WebSocket opens. - * 400: - * description: Invalid request or unsupported connection type - * 403: - * description: Access denied to host - * 404: - * description: Host not found - * 500: - * description: Server error - */ -router.post( - "/connect-host/:hostId", - async (req: express.Request, res: express.Response) => { +export function registerRoutes(router: Router, deps: RouteDeps): void { + const { ctx, log, tokens, sessions } = deps; + + const refuseWhenOff = async (res: Response): Promise => { + if (await deps.enabled()) return false; + res.status(403).json({ error: "Remote Desktop is turned off" }); + return true; + }; + + /** + * @openapi + * /plugin-api/remote-desktop/connection/{connectId}: + * get: + * summary: Look up guacd's id for a session + * description: Returns the guacamole connection id for a connect request, once guacd has opened it. Only the session's owner gets an answer. + * tags: + * - Remote Desktop + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: connectId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The id, or null while guacd has not opened the session. + * 401: + * description: Not signed in. + */ + router.get("/connection/:connectId", (req: Request, res: Response) => { + const userId = actor(ctx, res); + if (!userId) return; + const session = sessions.byConnect(String(req.params.connectId)); + res.json({ + guacamoleConnectionId: + session?.ownerUserId === userId ? session.guacamoleConnectionId : null, + }); + }); + + /** + * @openapi + * /plugin-api/remote-desktop/token: + * post: + * summary: Mint a connection token for an address + * description: Creates an encrypted guacamole-lite token for connection details the caller supplies, used by Quick Connect. No saved host is involved. + * tags: + * - Remote Desktop + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - type + * - hostname + * properties: + * type: + * type: string + * enum: [rdp, vnc, telnet] + * hostname: + * type: string + * port: + * type: integer + * username: + * type: string + * password: + * type: string + * domain: + * type: string + * responses: + * 200: + * description: The encrypted token. + * 400: + * description: Missing or invalid fields. + * 403: + * description: Remote Desktop is turned off. + * 500: + * description: Server error. + */ + router.post("/token", async (req: Request, res: Response) => { try { - const userId = (req as AuthenticatedRequest).userId!; - const hostId = Number.parseInt(String(req.params.hostId), 10); - - if (!hostId || isNaN(hostId)) { - return res.status(400).json({ error: "Invalid host ID" }); + if (await refuseWhenOff(res)) return; + const { type, hostname, port, username, password, domain, ...raw } = + (req.body ?? {}) as Record; + if (!type || !hostname) { + return res + .status(400) + .json({ error: "Missing required fields: type and hostname" }); } - - const hostResolutionRepository = createCurrentHostResolutionRepository(); - // Decrypt under the owner's DEK; shared hosts carry owner-encrypted fields. - const hostOwnerId = - await hostResolutionRepository.findHostOwnerId(hostId); - const host = hostOwnerId - ? await hostResolutionRepository.findHostById(hostId, hostOwnerId) - : null; - - if (!host) { - return res.status(404).json({ error: "Host not found" }); - } - - if (host.userId !== userId) { - const permissionManager = PermissionManager.getInstance(); - const accessInfo = await permissionManager.canAccessHost( - userId, - hostId, - "connect", - ); - - if (!accessInfo.hasAccess) { - guacLogger.warn("User attempted to access host without permission", { - operation: "guac_access_denied", - userId, - hostId, - }); - return res.status(403).json({ error: "Access denied to this host" }); - } - } - - const requestedProtocol = req.body?.protocol as string | undefined; - const connectionType = - requestedProtocol || (host.connectionType as string); - - if (!["rdp", "vnc", "telnet"].includes(connectionType)) { + if (!isRemoteProtocol(type)) { return res.status(400).json({ - error: `Connection type '${connectionType}' is not supported for remote desktop. Only RDP, VNC, and Telnet are supported.`, + error: "Invalid connection type. Must be rdp, vnc, or telnet", }); } - - // Old hosts only had connectionType set; enableRdp/enableVnc/enableTelnet defaulted to false. - // Apply the same migration fallback used in host.ts GET routes. - const ct = host.connectionType as string; - const rdpRaw = !!host.enableRdp; - const vncRaw = !!host.enableVnc; - const telRaw = !!host.enableTelnet; - const isMigratedNonSsh = - !rdpRaw && !vncRaw && !telRaw && ct && ct !== "ssh"; - const protocolEnabledMap: Record = { - rdp: isMigratedNonSsh ? ct === "rdp" : rdpRaw, - vnc: isMigratedNonSsh ? ct === "vnc" : vncRaw, - telnet: isMigratedNonSsh ? ct === "telnet" : telRaw, - }; - if (!protocolEnabledMap[connectionType]) { - return res.status(400).json({ - error: `${connectionType.toUpperCase()} is not enabled for this host.`, - }); + const options: Record = {}; + for (const [key, value] of Object.entries(raw)) { + if (value !== "auto") options[key] = value; } - - let guacConfig: Record = {}; - if (host.guacamoleConfig) { - try { - guacConfig = - typeof host.guacamoleConfig === "string" - ? JSON.parse(host.guacamoleConfig as string) - : (host.guacamoleConfig as Record); - } catch (error) { - guacLogger.warn("Failed to parse guacamole config", { - operation: "guac_config_parse_error", - hostId, - error: getErrorMessage(error), - }); - } - } - - // Strip "auto" sentinel values — these mean "use guacd default" in the UI - // but guacd doesn't recognise "auto" as a valid parameter value. - for (const key of Object.keys(guacConfig)) { - if (guacConfig[key] === "auto") { - delete guacConfig[key]; - } - } - - // Extract per-connection guacd proxy settings before passing the rest as connection settings - const perConnectionGuacdHost = guacConfig["guacd-hostname"] as - string | undefined; - const perConnectionGuacdPortRaw = guacConfig["guacd-port"]; - const perConnectionGuacdPort = perConnectionGuacdPortRaw - ? parseInt(String(perConnectionGuacdPortRaw), 10) || undefined - : undefined; - delete guacConfig["guacd-hostname"]; - delete guacConfig["guacd-port"]; - - if (guacConfig.dpi != null) { - const parsed = parseInt(String(guacConfig.dpi), 10); - guacConfig.dpi = - Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; - } - - const hostRecord = host as Record; - const hostRepository = hostResolutionRepository; - const isSharedConnection = host.userId !== userId; - let sharedAuthResolution: RecipientSharedHostAuthResolution | null = null; - - if (isSharedConnection) { - // Recipients never read the owner's raw secrets; wipe them and use - // the per-recipient snapshot for the requested protocol instead. - host.password = null; - host.rdpUser = null; - host.rdpPassword = null; - host.vncUser = null; - host.vncPassword = null; - host.telnetUser = null; - host.telnetPassword = null; - - try { - const resolution = await resolveRecipientSharedHostAuthentication( - host, - hostId, - userId, - connectionType as AuthOverrideProtocol, - ); - sharedAuthResolution = resolution; - const auth = - resolution.source === "personal-override" - ? { ...resolution.credential, domain: null } - : resolution.source === "owner-shared" - ? resolution.secret - : null; - if (auth) { - if (connectionType === "rdp") { - host.rdpUser = auth.username ?? null; - host.rdpPassword = auth.password ?? null; - if (auth.domain) host.rdpDomain = auth.domain; - } else if (connectionType === "vnc") { - host.vncUser = auth.username ?? null; - host.vncPassword = auth.password ?? null; - } else if (connectionType === "telnet") { - host.telnetUser = auth.username ?? null; - host.telnetPassword = auth.password ?? null; - } - } - } catch (e) { - guacLogger.warn("Failed to resolve shared host auth", { - operation: "guac_shared_auth_resolve", - hostId, - protocol: connectionType, - error: getErrorMessage(e, "Unknown"), - }); - } - } else { - // Backward compat: if authType is not stored but a credentialId is, treat as credential mode - const rdpEffectiveAuthType = - (host.rdpAuthType as string) || - (host.rdpCredentialId ? "credential" : "direct"); - const vncEffectiveAuthType = - (host.vncAuthType as string) || - (host.vncCredentialId ? "credential" : "direct"); - const telnetEffectiveAuthType = - (host.telnetAuthType as string) || - (hostRecord.telnetCredentialId ? "credential" : "direct"); - - if (rdpEffectiveAuthType === "credential" && host.rdpCredentialId) { - try { - const cred = await hostRepository.findCredentialByIdForUser( - host.rdpCredentialId as number, - host.userId as string, - ); - if (cred) { - if (cred.username) host.rdpUser = cred.username; - if (cred.password) host.rdpPassword = cred.password; - // domain is never sourced from credential - } - } catch (e) { - guacLogger.warn("Failed to resolve RDP credential", { - operation: "guac_rdp_credential_resolve", - hostId, - error: getErrorMessage(e, "Unknown"), - }); - } - } - - if (vncEffectiveAuthType === "credential" && host.vncCredentialId) { - try { - const cred = await hostRepository.findCredentialByIdForUser( - host.vncCredentialId as number, - host.userId as string, - ); - if (cred) { - if (cred.password) host.vncPassword = cred.password; - if (cred.username) host.vncUser = cred.username; - } - } catch (e) { - guacLogger.warn("Failed to resolve VNC credential", { - operation: "guac_vnc_credential_resolve", - hostId, - error: getErrorMessage(e, "Unknown"), - }); - } - } - - if ( - telnetEffectiveAuthType === "credential" && - hostRecord.telnetCredentialId - ) { - try { - const cred = await hostRepository.findCredentialByIdForUser( - hostRecord.telnetCredentialId as number, - host.userId as string, - ); - if (cred) { - if (cred.username) host.telnetUser = cred.username; - if (cred.password) host.telnetPassword = cred.password; - } - } catch (e) { - guacLogger.warn("Failed to resolve Telnet credential", { - operation: "guac_telnet_credential_resolve", - hostId, - error: getErrorMessage(e, "Unknown"), - }); - } + const host = String(hostname); + const user = username ? String(username) : ""; + const pass = password ? String(password) : ""; + const targetPort = Number(port) || DEFAULT_PORT[type]; + const userId = ctx.currentActor(); + if (type === "rdp" && userId) { + for (const [key, value] of Object.entries( + await readUserDefaults(ctx, userId), + )) { + if (options[key] === undefined) options[key] = value; } } let token: string; - let hostname = host.ip as string; - let port = host.port as number; - let username: string; - let password: string; - - const rdpAuthTypeForConnect = resolveRdpAuthTypeForConnect({ - storedAuthType: host.rdpAuthType as string | null, - credentialId: host.rdpCredentialId as number | null, - sharedResolution: isSharedConnection ? sharedAuthResolution : undefined, - }); - - switch (connectionType) { - case "rdp": - if (rdpAuthTypeForConnect === "none") { - username = String(req.body?.promptedUsername || ""); - password = String(req.body?.promptedPassword || ""); - } else { - username = - (host.rdpUser as string) || (host.username as string) || ""; - password = - (host.rdpPassword as string) || (host.password as string) || ""; - } - port = (host.rdpPort as number) || port || 3389; - break; - case "vnc": - username = (host.vncUser as string) || ""; - password = - (host.vncPassword as string) || (host.password as string) || ""; - port = (host.vncPort as number) || port || 5900; - break; - case "telnet": - username = (host.telnetUser as string) || ""; - password = - (host.telnetPassword as string) || (host.password as string) || ""; - port = (host.telnetPort as number) || port || 23; - break; - default: - username = ""; - password = ""; + if (type === "rdp") { + token = tokens.createRdpToken(host, user, pass, { + port: targetPort, + domain: domain ? String(domain) : undefined, + ...options, + }); + } else if (type === "vnc") { + token = tokens.createVncToken(host, user || undefined, pass, { + port: targetPort, + ...options, + }); + } else { + token = tokens.createTelnetToken(host, user, pass, { + port: targetPort, + ...options, + }); } - const storedDomain = - (host.rdpDomain as string) || (host.domain as string) || ""; + res.json({ token }); + } catch (error) { + log.error("Failed to generate a connection token", { + operation: "guac_token_error", + error: errorMessage(error), + }); + res.status(500).json({ error: "Failed to generate connection token" }); + } + }); + + /** + * @openapi + * /plugin-api/remote-desktop/connect-host/{hostId}: + * post: + * summary: Mint a connection token for a saved host + * description: Resolves the host's RDP, VNC or Telnet login for the caller (a shared recipient gets only what was shared with them), opens a jump tunnel when the host has jump hosts, and returns an encrypted guacamole-lite token. + * tags: + * - Remote Desktop + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: hostId + * required: true + * schema: + * type: integer + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * protocol: + * type: string + * enum: [rdp, vnc, telnet] + * promptedUsername: + * type: string + * promptedPassword: + * type: string + * promptedDomain: + * type: string + * tabInstanceId: + * type: string + * responses: + * 200: + * description: The token and the id to look the session up by. + * 400: + * description: Invalid host id, or the protocol is off for this host. + * 403: + * description: Remote Desktop is turned off. + * 404: + * description: Host not found or no access. + * 500: + * description: The tunnel or the token could not be set up. + */ + router.post("/connect-host/:hostId", async (req: Request, res: Response) => { + try { + const userId = actor(ctx, res); + if (!userId) return; + if (await refuseWhenOff(res)) return; + + const hostId = Number.parseInt(String(req.params.hostId), 10); + if (!hostId) return res.status(400).json({ error: "Invalid host ID" }); + + const body = (req.body ?? {}) as Record; + const settings = await readHostSettings(ctx, hostId); + const requested = body.protocol; + const protocol: RemoteProtocol | undefined = isRemoteProtocol(requested) + ? requested + : (["rdp", "vnc", "telnet"] as const).find( + (candidate) => settings[ENABLE_KEY[candidate]], + ); + if (!protocol) { + return res + .status(400) + .json({ error: "Remote Desktop is not enabled for this host." }); + } + + const target = await ctx.credentials.resolveHostProtocol( + hostId, + protocol, + ); + if (!target) { + return res.status(404).json({ error: "Host not found" }); + } + if (!settings[ENABLE_KEY[protocol]]) { + return res.status(400).json({ + error: `${protocol.toUpperCase()} is not enabled for this host.`, + }); + } + + // The user's RDP defaults sit under whatever the host sets itself. + const { config, guacdOverrides } = cleanGuacConfig({ + ...(protocol === "rdp" ? await readUserDefaults(ctx, userId) : {}), + ...settings.guacamoleConfig, + }); + let guacConfig = config; + + const promptForLogin = + protocol === "rdp" && target.auth.authType === "none"; + const username = promptForLogin + ? String(body.promptedUsername || "") + : target.auth.username; + const password = promptForLogin + ? String(body.promptedPassword || "") + : target.auth.password; const domain = resolveRdpDomain( - rdpAuthTypeForConnect, - req.body?.promptedDomain, - storedDomain, + target.auth.authType, + body.promptedDomain, + target.auth.domain, ); - // Establish SSH tunnel if jump hosts are configured - let jumpHosts: Array<{ hostId: number }> = []; - if (host.jumpHosts) { - try { - jumpHosts = - typeof host.jumpHosts === "string" - ? JSON.parse(host.jumpHosts as string) - : (host.jumpHosts as Array<{ hostId: number }>); - } catch { - jumpHosts = []; + let hostname = target.host.ip; + let port = + (settings[PORT_KEY[protocol]] as number) || DEFAULT_PORT[protocol]; + const termixConnectId = crypto.randomUUID(); + const cleanups: Array<() => void> = []; + const jumpHosts = target.host.jumpHosts; + const needsVncProxy = protocol === "vnc" && !username; + + const endpoint = + jumpHosts.length > 0 || needsVncProxy + ? resolveJumpTunnelEndpoint( + guacdOverrides.guacdHost || deps.guacd().host, + ) + : null; + + try { + if (jumpHosts.length > 0 && endpoint) { + const tunnel = await openJumpTunnel(ctx, log, { + jumpHosts, + targetHost: hostname, + targetPort: port, + bindHost: endpoint.bindHost, + connectId: termixConnectId, + }); + cleanups.push(tunnel.close); + hostname = endpoint.advertisedHost; + port = tunnel.port; } - } - let tunnelEndpoint: ReturnType | null = - null; - if (jumpHosts.length > 0 || (connectionType === "vnc" && !username)) { - let guacdUrl: string | undefined; - try { - guacdUrl = - (await createCurrentSettingsRepository().get("guac_url")) ?? - undefined; - } catch { - // Environment/default guacd configuration remains available. - } - const guacdHost = - perConnectionGuacdHost || resolveGuacdOptions(guacdUrl).host; - tunnelEndpoint = resolveJumpTunnelEndpoint(guacdHost); - } - - if (jumpHosts.length > 0) { - try { - // The chain dials the first hop through that hop's own SOCKS5 - // settings; the target host's proxy config does not apply to it. - const { client: jumpClient } = await pluginSsh().jumpChain( - jumpHosts as Array<{ hostId: number }>, - ); - - const targetHostname = hostname; - const targetPort = port; - const tunnelPort = await new Promise((resolve, reject) => { - const server = net.createServer((sock) => { - jumpClient.forwardOut( - "127.0.0.1", - 0, - targetHostname, - targetPort, - (err, stream) => { - if (err) { - sock.destroy(); - return; - } - sock.pipe(stream).pipe(sock); - }, - ); - }); - server.on("error", reject); - server.listen(0, tunnelEndpoint!.bindHost, () => { - const addr = server.address() as net.AddressInfo; - // Auto-cleanup after 1 hour - setTimeout( - () => { - server.close(); - jumpClient.end(); - }, - 60 * 60 * 1000, - ); - resolve(addr.port); - }); - }); - hostname = tunnelEndpoint!.advertisedHost; - port = tunnelPort; - guacLogger.info("SSH tunnel established for guacamole", { - operation: "guac_ssh_tunnel", - hostId, - tunnelPort, - }); - } catch (tunnelError) { - guacLogger.error("Failed to establish SSH tunnel", tunnelError, { - operation: "guac_ssh_tunnel_error", - hostId, - }); - return res.status(500).json({ - error: "Failed to establish SSH tunnel to remote host", - }); - } - } - - if (connectionType === "vnc" && !username) { - try { + if (needsVncProxy && endpoint) { const proxy = await createMacosVncCompatibilityProxy({ targetHost: hostname, targetPort: port, - bindHost: tunnelEndpoint!.bindHost, + bindHost: endpoint.bindHost, }); - hostname = tunnelEndpoint!.advertisedHost; + cleanups.push(proxy.close); + hostname = endpoint.advertisedHost; port = proxy.port; - setTimeout(proxy.close, 60 * 60 * 1000).unref(); - guacLogger.info("VNC compatibility proxy established", { - operation: "guac_vnc_compatibility_proxy", - hostId, - proxyPort: port, - }); - } catch (proxyError) { - guacLogger.error( - "Failed to establish VNC compatibility proxy", - proxyError, - { - operation: "guac_vnc_compatibility_proxy_error", - hostId, - }, - ); - return res.status(500).json({ - error: "Failed to establish VNC compatibility proxy", - }); } + } catch (error) { + for (const cleanup of cleanups) cleanup(); + log.error("Failed to reach the host through its jump hosts", { + operation: "guac_ssh_tunnel_error", + hostId, + error: errorMessage(error), + }); + return res + .status(500) + .json({ error: "Failed to establish SSH tunnel to remote host" }); } + sessions.park(termixConnectId, cleanups); - const guacdOverrides = { - ...(perConnectionGuacdHost - ? { guacdHost: perConnectionGuacdHost } - : {}), - ...(perConnectionGuacdPort - ? { guacdPort: perConnectionGuacdPort } - : {}), - }; + const recordings = deps.recordings(); const recordingEnabled = - connectionType !== "vnc" && host.enableSessionLogging !== false; + protocol !== "vnc" && + !!recordings && + (await recordings.enabledFor(hostId)); const recordingName = `${crypto.randomUUID()}.guac`; - const recordingPath = + const guacdRecordingPath = process.env.GUACD_RECORDING_PATH || process.env.GUACD_RECORDING_BACKEND_PATH || - path.resolve(DATA_DIR, "session_recordings", "guacamole"); - const recordingMetadata = recordingEnabled + path.resolve(recordingsDir()); + const recording = recordingEnabled ? { hostId, userId, - protocol: connectionType as "rdp" | "vnc" | "telnet", + protocol, path: recordingName, - guacdPath: recordingPath, + guacdPath: guacdRecordingPath, startedAt: new Date().toISOString(), } : undefined; if (recordingEnabled) { guacConfig = withRecordingSettings( guacConfig, - recordingPath, + guacdRecordingPath, recordingName, ); } - const termixConnectId = crypto.randomUUID(); const termixMeta = { termixConnectId, hostId, + hostName: target.host.name || target.host.ip, ownerUserId: userId, - protocol: connectionType as "rdp" | "vnc" | "telnet", + protocol, + tabInstanceId: + typeof body.tabInstanceId === "string" ? body.tabInstanceId : null, }; - switch (connectionType) { - case "rdp": - guacConfig = withDriveSettings(guacConfig, userId); - token = tokenService.createRdpToken( - hostname, - username, - password, - buildRdpSettings({ - port, - domain, - security: - (host.rdpSecurity as string) || - (host.security as string) || - undefined, - ignoreCert: - host.rdpIgnoreCert !== undefined - ? !!host.rdpIgnoreCert - : host.ignoreCert !== undefined - ? !!host.ignoreCert - : true, - guacConfig, - guacdOverrides, - }), - recordingMetadata, - termixMeta, - ); - break; - case "vnc": - token = tokenService.createVncToken( - hostname, - username || undefined, - password, - { - port, - ...guacConfig, - ...guacdOverrides, - }, - recordingMetadata, - termixMeta, - ); - break; - case "telnet": - token = tokenService.createTelnetToken( - hostname, - username, - password, - { - port, - ...guacConfig, - ...guacdOverrides, - }, - recordingMetadata, - termixMeta, - ); - break; - default: - return res.status(400).json({ error: "Invalid connection type" }); + let token: string; + if (protocol === "rdp") { + token = tokens.createRdpToken( + hostname, + username, + password, + buildRdpSettings({ + port, + domain, + security: settings.rdpSecurity || undefined, + ignoreCert: settings.rdpIgnoreCert, + guacConfig: withDriveSettings(guacConfig, userId), + guacdOverrides, + }), + recording, + termixMeta, + ); + } else if (protocol === "vnc") { + token = tokens.createVncToken( + hostname, + username || undefined, + password, + { port, ...guacConfig, ...guacdOverrides }, + recording, + termixMeta, + ); + } else { + token = tokens.createTelnetToken( + hostname, + username, + password, + { port, ...guacConfig, ...guacdOverrides }, + recording, + termixMeta, + ); } - const { ipAddress, userAgent } = getRequestMeta(req); - await logAudit({ - userId, - username: await getAuditUsername(userId), - action: `${connectionType}_connect`, + await ctx.audit.record({ + action: `${protocol}_connect`, resourceType: "host", resourceId: String(hostId), resourceName: `${hostname}:${port}`, - ipAddress, - userAgent, success: true, }); - res.json({ - token, - termixConnectId, - guacamoleConnectionId: null, - }); + res.json({ token, termixConnectId, guacamoleConnectionId: null }); } catch (error) { - guacLogger.error("Failed to generate guacamole token for host", error, { + log.error("Failed to generate a connection token for a host", { operation: "guac_host_token_error", + error: errorMessage(error), }); res.status(500).json({ error: "Failed to generate connection token" }); } - }, -); + }); -/** - * GET /guacamole/status - * Check if guacd is reachable - */ -router.get("/status", async (req, res) => { - try { - let dbUrl: string | undefined; + /** + * @openapi + * /plugin-api/remote-desktop/status: + * get: + * summary: Remote Desktop status + * description: Whether Remote Desktop is turned on, and whether guacd answers at the configured address. + * tags: + * - Remote Desktop + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: probe + * schema: + * type: string + * description: "0" skips dialing guacd. + * responses: + * 200: + * description: The status. + * 500: + * description: The check failed. + */ + router.get("/status", async (req: Request, res: Response) => { try { - dbUrl = - (await createCurrentSettingsRepository().get("guac_url")) ?? undefined; - } catch { - // Fall back to env vars - } - const { host: guacdHost, port: guacdPort } = resolveGuacdOptions(dbUrl); - - const net = await import("net"); - - const checkConnection = (): Promise => { - return new Promise((resolve) => { - const socket = new net.Socket(); - socket.setTimeout(3000); - - socket.on("connect", () => { - socket.destroy(); - resolve(true); - }); - - socket.on("timeout", () => { - socket.destroy(); - resolve(false); - }); - - socket.on("error", () => { - socket.destroy(); - resolve(false); - }); - - socket.connect(guacdPort, guacdHost); + const enabled = await deps.enabled(); + const guacd = deps.guacd(); + const probe = enabled && req.query.probe !== "0"; + const reachable = probe ? await probeGuacd(guacd) : false; + res.json({ + enabled, + guacd: { + host: guacd.host, + port: guacd.port, + status: reachable ? "connected" : "disconnected", + }, + websocket: { + path: "/plugin-ws/remote-desktop/display", + status: "running", + }, }); - }; - - const isConnected = await checkConnection(); + } catch (error) { + log.error("Failed to check Remote Desktop status", { + operation: "guac_status_error", + error: errorMessage(error), + }); + res.status(500).json({ error: "Failed to check status" }); + } + }); + /** + * @openapi + * /plugin-api/remote-desktop/native-rdp: + * get: + * summary: Whether the native RDP client can be opened + * description: True only in the Windows desktop app. + * tags: + * - Remote Desktop + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: Availability. + * post: + * summary: Open the native RDP client + * description: Opens the operating system's own RDP client (mstsc) for an address. The password is never passed; the client asks for it. Windows desktop app only. + * tags: + * - Remote Desktop + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - host + * properties: + * host: + * type: string + * port: + * type: integer + * username: + * type: string + * domain: + * type: string + * responses: + * 200: + * description: Whether the client opened. + * 400: + * description: Missing host. + * 404: + * description: Not running in the desktop app. + */ + router.get("/native-rdp", (_req: Request, res: Response) => { res.json({ - guacd: { - host: guacdHost, - port: guacdPort, - status: isConnected ? "connected" : "disconnected", - }, - websocket: { - path: "/plugin-ws/remote-desktop/display", - status: "running", - }, + available: ctx.desktop.available() && process.platform === "win32", }); - } catch (error) { - guacLogger.error("Failed to check guacamole status", error, { - operation: "guac_status_error", - }); - res.status(500).json({ error: "Failed to check status" }); - } -}); + }); -export { router }; - -export function startRemoteDesktopService(mountOn: Router): void { - mountOn.use(router); + router.post("/native-rdp", async (req: Request, res: Response) => { + if (!ctx.desktop.available()) { + return res.status(404).json({ error: "Only in the desktop app" }); + } + const body = (req.body ?? {}) as Record; + if (typeof body.host !== "string" || !body.host) { + return res.status(400).json({ error: "Missing host" }); + } + try { + const result = await ctx.desktop.launchNativeRdp({ + host: body.host, + port: Number(body.port) || DEFAULT_PORT.rdp, + username: typeof body.username === "string" ? body.username : undefined, + domain: typeof body.domain === "string" ? body.domain : undefined, + }); + res.json(result); + } catch (error) { + res.json({ success: false, error: errorMessage(error) }); + } + }); } diff --git a/plugins/remote-desktop/src/backend/sessions.ts b/plugins/remote-desktop/src/backend/sessions.ts new file mode 100644 index 000000000..083cc1e55 --- /dev/null +++ b/plugins/remote-desktop/src/backend/sessions.ts @@ -0,0 +1,116 @@ +import type { TermixGuacMeta } from "./token-service.js"; + +export interface RemoteSession { + guacamoleConnectionId: string; + termixConnectId: string; + hostId: number; + hostName: string; + ownerUserId: string; + protocol: TermixGuacMeta["protocol"]; + tabInstanceId: string | null; + openedAt: number; +} + +type Cleanup = () => void; + +/** How long a minted token's tunnel waits for guacd to connect through it. */ +export const PENDING_TIMEOUT_MS = 5 * 60 * 1000; + +/** + * Live guacd sessions and what each one holds open. + * + * A connect request builds its jump tunnel or VNC proxy before guacd dials + * in, so those resources are parked under the request's connect id and move + * onto the session when it opens. They are released when the session + * closes, when guacd never shows up, or when the plugin stops. + */ +export class RemoteSessions { + private readonly pending = new Map< + string, + { cleanups: Cleanup[]; timer: ReturnType } + >(); + private readonly byConnectId = new Map(); + private readonly byGuacamoleId = new Map(); + private readonly cleanupsByConnectId = new Map(); + + constructor(private readonly pendingTimeoutMs = PENDING_TIMEOUT_MS) {} + + /** Resources for a connection that guacd has not opened yet. */ + park(termixConnectId: string, cleanups: Cleanup[]): void { + if (cleanups.length === 0) return; + const timer = setTimeout(() => { + this.pending.delete(termixConnectId); + run(cleanups); + }, this.pendingTimeoutMs); + timer.unref?.(); + this.pending.set(termixConnectId, { cleanups, timer }); + } + + opened( + meta: TermixGuacMeta, + guacamoleConnectionId: string, + extra: Cleanup[] = [], + ): RemoteSession { + const session: RemoteSession = { + guacamoleConnectionId, + termixConnectId: meta.termixConnectId, + hostId: meta.hostId, + hostName: meta.hostName, + ownerUserId: meta.ownerUserId, + protocol: meta.protocol, + tabInstanceId: meta.tabInstanceId ?? null, + openedAt: Date.now(), + }; + const parked = this.pending.get(meta.termixConnectId); + if (parked) { + clearTimeout(parked.timer); + this.pending.delete(meta.termixConnectId); + } + this.cleanupsByConnectId.set(meta.termixConnectId, [ + ...(parked?.cleanups ?? []), + ...extra, + ]); + this.byConnectId.set(meta.termixConnectId, session); + this.byGuacamoleId.set(guacamoleConnectionId, session); + return session; + } + + closed(termixConnectId: string): void { + const session = this.byConnectId.get(termixConnectId); + if (session) this.byGuacamoleId.delete(session.guacamoleConnectionId); + this.byConnectId.delete(termixConnectId); + run(this.cleanupsByConnectId.get(termixConnectId) ?? []); + this.cleanupsByConnectId.delete(termixConnectId); + } + + byConnect(termixConnectId: string): RemoteSession | null { + return this.byConnectId.get(termixConnectId) ?? null; + } + + byGuacamole(guacamoleConnectionId: string): RemoteSession | null { + return this.byGuacamoleId.get(guacamoleConnectionId) ?? null; + } + + /** Releases everything, for deactivate. */ + clear(): void { + for (const { cleanups, timer } of this.pending.values()) { + clearTimeout(timer); + run(cleanups); + } + this.pending.clear(); + for (const cleanups of this.cleanupsByConnectId.values()) run(cleanups); + this.cleanupsByConnectId.clear(); + this.byConnectId.clear(); + this.byGuacamoleId.clear(); + } +} + +function run(cleanups: Cleanup[]): void { + for (const cleanup of cleanups) { + try { + cleanup(); + } catch { + // One failed close must not keep the rest open. + } + } +} diff --git a/plugins/remote-desktop/src/backend/ssh.ts b/plugins/remote-desktop/src/backend/ssh.ts deleted file mode 100644 index b7a221543..000000000 --- a/plugins/remote-desktop/src/backend/ssh.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Client } from "ssh2"; -import type { - PluginSsh, - PluginSshConnection, - PluginSshConnectOptions, - PluginSshHost, -} from "@termix/plugin-sdk/backend"; - -let current: PluginSsh | null = null; - -/** Set in activate, cleared on deactivate. */ -export function setPluginSsh(ssh: PluginSsh | null): void { - current = ssh; -} - -export function pluginSsh(): PluginSsh { - if (!current) throw new Error("The plugin is not active"); - return current; -} - -/** withConnection with ssh2's Client type filled in. */ -export function withSshConnection( - host: number | PluginSshHost, - options: PluginSshConnectOptions & { pool: string }, - fn: (client: Client) => Promise, -): Promise { - return pluginSsh().withConnection(host, options, fn); -} - -/** connect with ssh2's Client type filled in. */ -export function connectSsh( - host: number | PluginSshHost, - options?: PluginSshConnectOptions, -): Promise> { - return pluginSsh().connect(host, options); -} diff --git a/plugins/remote-desktop/src/backend/token-service.ts b/plugins/remote-desktop/src/backend/token-service.ts index fd6d00e35..4e6d96afa 100644 --- a/plugins/remote-desktop/src/backend/token-service.ts +++ b/plugins/remote-desktop/src/backend/token-service.ts @@ -1,5 +1,9 @@ import crypto from "crypto"; -import { guacLogger } from "../../../../src/backend/utils/logger.js"; + +interface TokenLogger { + warn: (message: string, meta?: Record) => void; + error: (message: string, meta?: Record) => void; +} export interface GuacamoleConnectionSettings { type?: "rdp" | "vnc" | "telnet"; @@ -33,8 +37,10 @@ export interface GuacamoleConnectionSettings { export interface TermixGuacMeta { termixConnectId: string; hostId: number; + hostName: string; ownerUserId: string; protocol: "rdp" | "vnc" | "telnet"; + tabInstanceId: string | null; } export interface GuacamoleToken { @@ -57,21 +63,19 @@ export interface GuacamoleRecordingMetadata { const CIPHER = "aes-256-cbc"; const KEY_LENGTH = 32; +const silentLogger: TokenLogger = { warn: () => {}, error: () => {} }; + +/** + * Encrypts the connection tokens guacamole-lite decrypts. Built in activate; + * the key is derived from the environment, so a restart keeps it. + */ export class GuacamoleTokenService { - private static instance: GuacamoleTokenService; private encryptionKey: Buffer; - private constructor() { + constructor(private readonly log: TokenLogger = silentLogger) { this.encryptionKey = this.initializeKey(); } - static getInstance(): GuacamoleTokenService { - if (!GuacamoleTokenService.instance) { - GuacamoleTokenService.instance = new GuacamoleTokenService(); - } - return GuacamoleTokenService.instance; - } - private initializeKey(): Buffer { const existingKey = process.env.GUACAMOLE_ENCRYPTION_KEY; if (existingKey) { @@ -91,12 +95,9 @@ export class GuacamoleTokenService { .digest(); } - guacLogger.warn( - "No persistent encryption key found, generating random key", - { - operation: "guac_key_generation", - }, - ); + this.log.warn("No persistent encryption key found, generating random key", { + operation: "guac_key_generation", + }); return crypto.randomBytes(KEY_LENGTH); } @@ -134,8 +135,9 @@ export class GuacamoleTokenService { return JSON.parse(decrypted) as GuacamoleToken; } catch (error) { - guacLogger.error("Failed to decrypt guacamole token", error, { + this.log.error("Failed to decrypt guacamole token", { operation: "guac_token_decrypt_error", + error: error instanceof Error ? error.message : String(error), }); return null; } diff --git a/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx b/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx index 0e19b9115..e598980cd 100644 --- a/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx +++ b/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx @@ -1,5 +1,4 @@ import { watchGuacamoleConnectionId } from "./guacamole-session-id"; -import { getErrorMessage } from "@/lib/error-message.js"; import React, { useState, useEffect, @@ -14,40 +13,42 @@ import { type GuacamoleDisplayHandle, type GuacamoleTouchMode, } from "./GuacamoleDisplay.tsx"; -import { getSSHHosts, logActivity, isElectron } from "@/main-axios"; import { getGuacamoleTokenFromHost, getGuacdStatus } from "./guacamole-api"; import { readConfiguredDimension } from "./guacamole-display-size.ts"; -import { getGuacamoleToken, parseGuacamoleConfig } from "./guacamole-api"; -import { - resolveConnectionOrigin, - type ConnectionOrigin, -} from "@/lib/connection-origin.ts"; +import { getGuacamoleToken } from "./guacamole-api"; import { + useHost, useTranslation, useConnectionRetry, } from "@termix/plugin-sdk/frontend"; -import { GuacamoleToolbar } from "./GuacamoleToolbar.tsx"; -import { GuacamoleFileBrowser } from "./GuacamoleFileBrowser.tsx"; -import { describeUploadError } from "./guacamole-filesystem.ts"; -import { canUploadToRdpDrive } from "./guacamole-file-drop.ts"; -import { Button } from "@/components/button.tsx"; -import { Input } from "@/components/input.tsx"; -import { PasswordInput } from "@/components/password-input.tsx"; import { + Button, + ConnectionLogProvider, + ConnectionScreen, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@/components/dialog.tsx"; -import { ConnectionScreen } from "@/components/connection/ConnectionScreen.tsx"; -import { - ConnectionLogProvider, + Input, + PasswordInput, + isElectron, + logActivity, + resolveConnectionOrigin, useConnectionLog, -} from "@/ssh/connection-log/ConnectionLogContext.tsx"; -import type { SSHHost } from "@/types"; -import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext"; -import { resolveConnectionDefaults } from "@/lib/connection-defaults"; + type ConnectionOrigin, +} from "@termix/plugin-sdk/ui"; +import type { GuacamoleConfig } from "./guacamole-config"; +import { + errorMessage, + hostRemoteOptions, + type Protocol, + type RemoteHostLogin, +} from "./host-remote"; +import { GuacamoleToolbar } from "./GuacamoleToolbar.tsx"; +import { GuacamoleFileBrowser } from "./GuacamoleFileBrowser.tsx"; +import { describeUploadError } from "./guacamole-filesystem.ts"; +import { canUploadToRdpDrive } from "./guacamole-file-drop.ts"; import { needsRdpCredentialPrompt } from "./rdp-credential-prompt"; interface GuacamoleAppProps { @@ -59,10 +60,48 @@ interface GuacamoleAppProps { quickConnectHost?: GuacamoleQuickHost; } -/** What GuacamoleApp needs from a host that has no database row. */ -export type GuacamoleQuickHost = GuacamoleAppInnerProps["hostConfig"] & { +/** + * What a session needs from its host. A saved host brings its options; a + * quick-connect host has no row, so it also carries its address and login. + */ +export interface GuacamoleHostConfig { name?: string; -}; + ip: string; + connectionType?: Protocol; + domain?: string; + guacamoleConfig?: GuacamoleConfig; + rdpAuthType?: string; + authOverrides?: RemoteHostLogin["authOverrides"]; + syncId?: string | null; + connectionOrigin?: "local" | "remote" | null; + showToolbar?: boolean; + port?: number; + username?: string; + password?: string; +} + +export type GuacamoleQuickHost = GuacamoleHostConfig; + +function savedHostConfig( + host: RemoteHostLogin, + protocol: Protocol | undefined, +): GuacamoleHostConfig { + const options = hostRemoteOptions(host); + return { + name: host.name ?? undefined, + ip: host.ip ?? "", + connectionType: + protocol ?? + (options.enableRdp ? "rdp" : options.enableVnc ? "vnc" : "telnet"), + domain: host.domain, + guacamoleConfig: options.guacamoleConfig, + rdpAuthType: host.rdpAuthType, + authOverrides: host.authOverrides, + syncId: host.syncId, + connectionOrigin: host.connectionOrigin, + showToolbar: options.enableToolbar, + }; +} export interface GuacamoleAppHandle { disconnect: () => void; @@ -77,50 +116,20 @@ const GuacamoleApp = React.forwardRef( ref, ) { const { t } = useTranslation(); - const defaults = useConnectionDefaults(); - const [hostConfig, setHostConfig] = useState( - null, - ); - const [loading, setLoading] = useState(true); - + const savedHost = useHost(quickConnectHost ? undefined : hostId); + const hostConfig: GuacamoleHostConfig | null = quickConnectHost + ? quickConnectHost + : savedHost + ? savedHostConfig(savedHost as RemoteHostLogin, protocol) + : null; + // The host list loads with the shell; a tab restored before it arrives + // waits for it instead of reporting the host missing. + const [waited, setWaited] = useState(false); useEffect(() => { - if (!defaults.ready) return; - if (quickConnectHost) { - const connectionType = protocol ?? quickConnectHost.connectionType; - setHostConfig({ - ...quickConnectHost, - guacamoleConfig: resolveConnectionDefaults( - connectionType === "rdp" ? defaults.rdp : {}, - {}, - ), - }); - setLoading(false); - return; - } - if (!hostId) { - setLoading(false); - return; - } - getSSHHosts() - .then((hosts) => { - const host = hosts.find((h) => h.id === parseInt(hostId, 10)); - if (!host) { - setHostConfig(null); - return; - } - const connectionType = protocol ?? host.connectionType; - const protocolDefaults = connectionType === "rdp" ? defaults.rdp : {}; - setHostConfig({ - ...host, - guacamoleConfig: resolveConnectionDefaults( - protocolDefaults, - parseGuacamoleConfig(host.guacamoleConfig), - ), - }); - }) - .catch(() => setHostConfig(null)) - .finally(() => setLoading(false)); - }, [hostId, protocol, defaults.ready, defaults.rdp, quickConnectHost]); + const timer = setTimeout(() => setWaited(true), 3000); + return () => clearTimeout(timer); + }, []); + const loading = !hostConfig && !!hostId && !waited; if (loading) { return ( @@ -159,23 +168,7 @@ const GuacamoleApp = React.forwardRef( interface GuacamoleAppInnerProps { hostId: number; - hostConfig: Pick< - SSHHost, - | "connectionType" - | "domain" - | "guacamoleConfig" - | "rdpAuthType" - | "authOverrides" - | "syncId" - | "connectionOrigin" - | "ip" - | "rdpPort" - | "vncPort" - | "rdpUser" - | "rdpPassword" - | "vncUser" - | "vncPassword" - > & { enableTerminalToolbar?: boolean }; + hostConfig: GuacamoleHostConfig; hostName: string; tabId?: string; protocol?: "rdp" | "vnc" | "telnet"; @@ -223,7 +216,7 @@ const GuacamoleAppInner = React.forwardRef< const [toolbarHidden, setToolbarHidden] = useState(false); const [pendingUploads, setPendingUploads] = useState([]); - const guacConfig = parseGuacamoleConfig(hostConfig.guacamoleConfig); + const guacConfig = hostConfig.guacamoleConfig ?? {}; const allowUpload = guacConfig.disableUpload !== true; const allowDownload = guacConfig.disableDownload !== true; @@ -311,10 +304,10 @@ const GuacamoleAppInner = React.forwardRef< let resolvedOrigin: ConnectionOrigin = "local"; if (isElectron()) { - resolvedOrigin = await resolveConnectionOrigin({ - connectionType: resolvedProtocolForConnect, - connectionOrigin: hostConfig.connectionOrigin, - }); + resolvedOrigin = await resolveConnectionOrigin( + { connectionOrigin: hostConfig.connectionOrigin }, + { defaultRemote: true }, + ); if (resolvedOrigin === "remote") { const remoteConfig = (await window.electronAPI?.invoke?.( "get-remote-sync-config", @@ -331,6 +324,9 @@ const GuacamoleAppInner = React.forwardRef< message: t("remoteDesktop.checkingGuacd"), }); const status = await getGuacdStatus(resolvedOrigin); + if (status.enabled === false) { + throw new Error(t("remoteDesktop.disabled")); + } if (status.guacd.status !== "connected") { throw new Error(t("remoteDesktop.guacdUnavailable")); } @@ -351,21 +347,12 @@ const GuacamoleAppInner = React.forwardRef< { protocol: resolvedProtocolForConnect, hostname: hostConfig.ip, - port: - resolvedProtocolForConnect === "vnc" - ? hostConfig.vncPort - : hostConfig.rdpPort, - username: - resolvedProtocolForConnect === "vnc" - ? hostConfig.vncUser - : hostConfig.rdpUser, - password: - resolvedProtocolForConnect === "vnc" - ? hostConfig.vncPassword - : hostConfig.rdpPassword, + port: hostConfig.port, + username: hostConfig.username, + password: hostConfig.password, domain: hostConfig.domain, ignoreCert: true, - guacamoleConfig: parseGuacamoleConfig(hostConfig.guacamoleConfig), + guacamoleConfig: hostConfig.guacamoleConfig, }, resolvedOrigin, ) @@ -375,6 +362,7 @@ const GuacamoleAppInner = React.forwardRef< protocol, promptedCredentials ?? undefined, hostConfig.syncId, + tabId, ); if (result) { setSessionLookup( @@ -397,6 +385,7 @@ const GuacamoleAppInner = React.forwardRef< promptedCredentials, resolvedProtocolForConnect, hostConfig, + tabId, addLog, t, ]); @@ -407,10 +396,7 @@ const GuacamoleAppInner = React.forwardRef< await fetchToken(); tokenRetryRef.current.markConnected(); } catch (err: unknown) { - const message = getErrorMessage( - err, - t("remoteDesktop.failedToConnect"), - ); + const message = errorMessage(err, t("remoteDesktop.failedToConnect")); setError(message || t("remoteDesktop.failedToConnect")); addLog({ type: "error", stage: "error", message }); tokenRetryRef.current.markFailed(); @@ -632,7 +618,7 @@ const GuacamoleAppInner = React.forwardRef< onClose={() => setFileBrowserOpen(false)} /> )} - {hostConfig.enableTerminalToolbar !== false && !toolbarHidden && ( + {hostConfig.showToolbar !== false && !toolbarHidden && ( ( - key: K, - value: HostEditorForm[K], -) => void; +type HostEditorForm = RemoteDesktopForm; +type HostEditorSetField = RemoteFormSetField; type GuacFieldSetter = (key: string, value: unknown) => void; @@ -49,7 +50,7 @@ export function HostEditorRdpTab({ form: HostEditorForm; setField: HostEditorSetField; setGuacField: GuacFieldSetter; - host?: Host | null; + host?: { macAddress?: string | null } | null; credentials?: { id: string; name: string; username: string }[]; }) { const { t } = useTranslation(); @@ -984,7 +985,7 @@ export function HostEditorVncTab({ form: HostEditorForm; setField: HostEditorSetField; setGuacField: GuacFieldSetter; - host?: Host | null; + host?: { macAddress?: string | null } | null; credentials?: { id: string; name: string; username: string }[]; }) { const { t } = useTranslation(); @@ -1523,7 +1524,6 @@ export function HostEditorTelnetTab({ form: HostEditorForm; setField: HostEditorSetField; setGuacField: GuacFieldSetter; - host?: Host | null; credentials?: { id: string; name: string; username: string }[]; }) { const { t } = useTranslation(); diff --git a/plugins/remote-desktop/src/frontend/guacamole-api.ts b/plugins/remote-desktop/src/frontend/guacamole-api.ts index f17cfd84e..1b06464bc 100644 --- a/plugins/remote-desktop/src/frontend/guacamole-api.ts +++ b/plugins/remote-desktop/src/frontend/guacamole-api.ts @@ -1,13 +1,18 @@ +import type { PluginApiClient, TermixApp } from "@termix/plugin-sdk/frontend"; import { - authApi, - getRemoteGuacamoleApi, - handleApiError, isElectron, -} from "@/main-axios"; -import type { AxiosInstance } from "axios"; -import type { GuacamoleConfig } from "@/types/guacamole-config"; -import { resolveRemoteHostId } from "@/lib/remote-server-api"; -import type { ConnectionOrigin } from "@/lib/connection-origin"; + resolveRemoteHostId, + type ConnectionOrigin, +} from "@termix/plugin-sdk/ui"; +import type { GuacamoleConfig } from "./guacamole-config"; +import { errorMessage } from "./host-remote"; + +let app: Pick | null = null; + +/** Set in activate, cleared on deactivate. */ +export function setRemoteDesktopApp(next: Pick | null) { + app = next; +} /** * Picks the backend that will actually serve this session, so the token and @@ -24,9 +29,13 @@ import type { ConnectionOrigin } from "@/lib/connection-origin"; * silently sent a missed call site to the remote server, which fails with a * bare network error on a desktop that has none configured. */ -function guacamoleApi(origin: ConnectionOrigin): AxiosInstance { - if (!isElectron()) return authApi; - return origin === "local" ? authApi : getRemoteGuacamoleApi(); +function guacamoleApi(origin: ConnectionOrigin): PluginApiClient { + if (!app) throw new Error("Remote Desktop is not active"); + return app.apiFor(origin); +} + +function apiError(error: unknown, action: string): Error { + return new Error(errorMessage(error, `Failed to ${action}`)); } export interface GuacamoleTokenRequest { @@ -51,18 +60,6 @@ type GuacamoleConfigSource = { guacamoleConfig?: string | Record | null; }; -export function parseGuacamoleConfig( - config?: string | GuacamoleConfig | null, -): GuacamoleConfig { - if (!config) return {}; - if (typeof config !== "string") return config; - try { - return JSON.parse(config) as GuacamoleConfig; - } catch { - return {}; - } -} - export function getGuacamoleDpi( source?: GuacamoleConfigSource, ): number | undefined { @@ -174,8 +171,8 @@ export async function getGuacamoleToken( try { const guacParams = toGuacamoleParams(request.guacamoleConfig); - const response = await guacamoleApi(origin).post( - "/plugin-api/remote-desktop/token", + const response = await guacamoleApi(origin).post( + "/token", { type: request.protocol, hostname: request.hostname, @@ -190,7 +187,7 @@ export async function getGuacamoleToken( ); return response.data; } catch (error) { - throw handleApiError(error, "get guacamole token"); + throw apiError(error, "get a connection token"); } } @@ -204,6 +201,7 @@ export async function getGuacamoleTokenFromHost( domain?: string; }, syncId?: string | null, + tabInstanceId?: string, ): Promise { try { // A locally-originated session is served by the embedded backend, which @@ -216,10 +214,11 @@ export async function getGuacamoleTokenFromHost( throw new Error("The synced host does not exist on the remote server"); } const targetHostId = remoteHostId ?? hostId; - const response = await guacamoleApi(origin).post( - `/plugin-api/remote-desktop/connect-host/${targetHostId}`, + const response = await guacamoleApi(origin).post( + `/connect-host/${targetHostId}`, { ...(protocol ? { protocol } : {}), + ...(tabInstanceId ? { tabInstanceId } : {}), ...(promptedCredentials?.username ? { promptedUsername: promptedCredentials.username } : {}), @@ -233,15 +232,22 @@ export async function getGuacamoleTokenFromHost( ); return response.data; } catch (error) { - throw handleApiError(error, "get guacamole token from host"); + throw apiError(error, "get a connection token"); } } -export async function getGuacdStatus(origin: ConnectionOrigin): Promise<{ +export interface RemoteDesktopStatus { + enabled: boolean; guacd: { status: string }; -}> { - const response = await guacamoleApi(origin).get( - "/plugin-api/remote-desktop/status", +} + +/** `probe: false` skips dialing guacd, for a caller that only needs `enabled`. */ +export async function getGuacdStatus( + origin: ConnectionOrigin, + options: { probe?: boolean } = {}, +): Promise { + const response = await guacamoleApi(origin).get( + options.probe === false ? "/status?probe=0" : "/status", ); return response.data; } @@ -251,9 +257,34 @@ export async function getGuacamoleConnectionId( origin: ConnectionOrigin, signal?: AbortSignal, ): Promise { - const response = await guacamoleApi(origin).get( - `/plugin-api/remote-desktop/connection/${encodeURIComponent(connectId)}`, - { signal }, - ); + const response = await guacamoleApi(origin).get<{ + guacamoleConnectionId?: string | null; + }>(`/connection/${encodeURIComponent(connectId)}`, { signal }); return response.data.guacamoleConnectionId ?? null; } + +export async function nativeRdpAvailable(): Promise { + if (!isElectron()) return false; + try { + const response = await guacamoleApi("local").get<{ available: boolean }>( + "/native-rdp", + ); + return response.data.available === true; + } catch { + return false; + } +} + +/** Always the embedded backend: it is the one that can reach Electron. */ +export async function openNativeRdp(request: { + host: string; + port?: number; + username?: string; + domain?: string; +}): Promise<{ success: boolean; error?: string }> { + const response = await guacamoleApi("local").post<{ + success: boolean; + error?: string; + }>("/native-rdp", request); + return response.data; +} diff --git a/src/types/guacamole-config.ts b/plugins/remote-desktop/src/frontend/guacamole-config.ts similarity index 100% rename from src/types/guacamole-config.ts rename to plugins/remote-desktop/src/frontend/guacamole-config.ts diff --git a/plugins/remote-desktop/src/frontend/guacamole-session-id.ts b/plugins/remote-desktop/src/frontend/guacamole-session-id.ts index 268ab5f8b..9b173472b 100644 --- a/plugins/remote-desktop/src/frontend/guacamole-session-id.ts +++ b/plugins/remote-desktop/src/frontend/guacamole-session-id.ts @@ -1,5 +1,5 @@ import { getGuacamoleConnectionId } from "./guacamole-api"; -import type { ConnectionOrigin } from "@/lib/connection-origin"; +import type { ConnectionOrigin } from "@termix/plugin-sdk/ui"; export function watchGuacamoleConnectionId( connectId: string, diff --git a/plugins/remote-desktop/src/frontend/host-remote.ts b/plugins/remote-desktop/src/frontend/host-remote.ts new file mode 100644 index 000000000..0e16cd36b --- /dev/null +++ b/plugins/remote-desktop/src/frontend/host-remote.ts @@ -0,0 +1,139 @@ +import type { PluginHostRecord } from "@termix/plugin-sdk/frontend"; +import type { GuacamoleConfig } from "./guacamole-config"; + +export const PLUGIN_ID = "remote-desktop"; + +export type Protocol = "rdp" | "vnc" | "telnet"; + +/** This plugin's host settings, as they arrive on host.pluginSettings. */ +export interface RemoteHostOptions { + enableRdp: boolean; + enableVnc: boolean; + enableTelnet: boolean; + rdpPort: number; + vncPort: number; + telnetPort: number; + rdpSecurity: string; + rdpIgnoreCert: boolean; + guacamoleConfig: GuacamoleConfig; + enableToolbar: boolean; +} + +export const ENABLE_KEY = { + rdp: "enableRdp", + vnc: "enableVnc", + telnet: "enableTelnet", +} as const satisfies Record; + +export const PORT_KEY = { + rdp: "rdpPort", + vnc: "vncPort", + telnet: "telnetPort", +} as const satisfies Record; + +export const DEFAULT_PORT: Record = { + rdp: 3389, + vnc: 5900, + telnet: 23, +}; + +/** The core fields holding a protocol's login, which stay on the host. */ +export interface RemoteHostLogin { + id?: number | string; + name?: string | null; + ip?: string; + domain?: string; + syncId?: string | null; + connectionOrigin?: "local" | "remote" | null; + rdpAuthType?: string; + rdpUser?: string; + rdpPassword?: string; + vncUser?: string; + vncPassword?: string; + authOverrides?: Record< + string, + { + credentialId?: number | string | null; + required?: boolean; + ownerAuthShared?: boolean; + } + >; + pluginSettings?: Record>; +} + +function port(value: unknown, fallback: number): number { + const parsed = typeof value === "string" ? Number(value) : value; + return typeof parsed === "number" && Number.isInteger(parsed) && parsed > 0 + ? parsed + : fallback; +} + +export function parseGuacamoleConfig(value: unknown): GuacamoleConfig { + if (!value) return {}; + if (typeof value === "string") { + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === "object" ? parsed : {}; + } catch { + return {}; + } + } + return typeof value === "object" ? (value as GuacamoleConfig) : {}; +} + +export function remoteOptions( + settings: Record | undefined, +): RemoteHostOptions { + const values = settings ?? {}; + return { + enableRdp: values.enableRdp === true, + enableVnc: values.enableVnc === true, + enableTelnet: values.enableTelnet === true, + rdpPort: port(values.rdpPort, DEFAULT_PORT.rdp), + vncPort: port(values.vncPort, DEFAULT_PORT.vnc), + telnetPort: port(values.telnetPort, DEFAULT_PORT.telnet), + rdpSecurity: + typeof values.rdpSecurity === "string" ? values.rdpSecurity : "", + rdpIgnoreCert: values.rdpIgnoreCert === true, + guacamoleConfig: parseGuacamoleConfig(values.guacamoleConfig), + enableToolbar: values.enableToolbar !== false, + }; +} + +export function hostRemoteOptions( + host: + | Pick + | PluginHostRecord + | null + | undefined, +): RemoteHostOptions { + const bag = (host as RemoteHostLogin | null | undefined)?.pluginSettings; + return remoteOptions(bag?.[PLUGIN_ID]); +} + +export function protocolEnabled( + host: + | Pick + | PluginHostRecord + | null + | undefined, + protocol: Protocol, +): boolean { + return hostRemoteOptions(host)[ENABLE_KEY[protocol]]; +} + +/** Quick Connect hosts are never saved; their ids carry this prefix. */ +const QUICK_CONNECT_ID_PREFIX = "quick-connect-"; + +export function isQuickConnectHost(host: { id?: unknown } | null | undefined) { + return String(host?.id ?? "").startsWith(QUICK_CONNECT_ID_PREFIX); +} + +export function errorMessage(error: unknown, fallback = "Unknown error") { + const data = (error as { response?: { data?: { error?: unknown } } }) + ?.response?.data?.error; + if (typeof data === "string" && data) return data; + if (error instanceof Error && error.message) return error.message; + if (typeof error === "string" && error) return error; + return fallback; +} diff --git a/plugins/remote-desktop/src/frontend/index.tsx b/plugins/remote-desktop/src/frontend/index.tsx index ada84fcdd..64f7f086e 100644 --- a/plugins/remote-desktop/src/frontend/index.tsx +++ b/plugins/remote-desktop/src/frontend/index.tsx @@ -1,23 +1,21 @@ import type { ComponentType, Ref } from "react"; -import i18next from "i18next"; -import { toast } from "sonner"; import { + Info, MessagesSquare, Monitor, MonitorUp, MousePointerClick, - Settings, } from "lucide-react"; import { useTranslation, type HostEditorSectionProps, + type PluginHostRecord, type StandaloneViewProps, type TabProps, type TermixApp, } from "@termix/plugin-sdk/frontend"; -import { FakeSwitch, SectionCard, SettingRow } from "@termix/plugin-sdk/ui"; -import type { Host } from "@/types/ui-types"; -import { isQuickConnectHost } from "@/sidebar/quick-connect-host"; +import { SectionCard, isElectron } from "@termix/plugin-sdk/ui"; +import { toast } from "sonner"; import GuacamoleApp, { type GuacamoleAppHandle } from "./GuacamoleApp"; import { GuacamoleDisplay } from "./GuacamoleDisplay"; import { @@ -25,48 +23,70 @@ import { HostEditorTelnetTab, HostEditorVncTab, } from "./HostEditorGuacamoleTabs"; -import { getGuacamoleTokenFromHost } from "./guacamole-api"; +import { + getGuacamoleTokenFromHost, + getGuacdStatus, + nativeRdpAvailable, + openNativeRdp, + setRemoteDesktopApp, +} from "./guacamole-api"; import { quickConnectGuacHost } from "./quick-connect-guac-host"; import { REMOTE_DESKTOP_TOOLBAR_SLOT } from "./GuacamoleToolbar.tsx"; - -type Protocol = "rdp" | "vnc" | "telnet"; +import { + DEFAULT_PORT, + ENABLE_KEY, + PORT_KEY, + hostRemoteOptions, + isQuickConnectHost, + protocolEnabled, + type Protocol, + type RemoteHostLogin, +} from "./host-remote"; +import { remoteDesktopForm } from "./remote-form"; const PROTOCOLS: { id: Protocol; titleKey: string; + descriptionKey: string; + paletteKey: string; icon: ComponentType<{ className?: string }>; - enableKey: "enableRdp" | "enableVnc" | "enableTelnet"; priority: number; order: number; + quickConnect?: { showDomain?: boolean }; }[] = [ { id: "rdp", titleKey: "hosts.tabRdp", + descriptionKey: "hosts.remoteDesktop", + paletteKey: "palette.connectRdp", icon: Monitor, - enableKey: "enableRdp", priority: 50, order: 100, + quickConnect: { showDomain: true }, }, { id: "vnc", titleKey: "hosts.tabVnc", + descriptionKey: "hosts.virtualNetwork", + paletteKey: "palette.connectVnc", icon: MousePointerClick, - enableKey: "enableVnc", priority: 40, order: 110, + quickConnect: {}, }, { id: "telnet", titleKey: "hosts.tabTelnet", + descriptionKey: "hosts.unencryptedShell", + paletteKey: "palette.connectTelnet", icon: MessagesSquare, - enableKey: "enableTelnet", priority: 30, order: 120, }, ]; function RemoteDesktopTab({ tab, host, isVisible, handleRef }: TabProps) { - const shellHost = host as unknown as Host; + const record = host as unknown as RemoteHostLogin | undefined; return ( } @@ -75,8 +95,8 @@ function RemoteDesktopTab({ tab, host, isVisible, handleRef }: TabProps) { protocol={tab.type as Protocol} isVisible={isVisible} quickConnectHost={ - isQuickConnectHost(shellHost) - ? quickConnectGuacHost(shellHost) + record && isQuickConnectHost(record) + ? quickConnectGuacHost(record) : undefined } /> @@ -111,79 +131,65 @@ function RemoteDisplay({ ); } -/** Applies one remote desktop field and stops inheriting user defaults. */ -function useGuacFieldSetter( - updateForm: HostEditorSectionProps["updateForm"], -): (key: string, value: unknown) => void { - return (key, value) => - updateForm((current) => ({ - inheritRemoteDesktopDefaults: false, - guacamoleConfig: { - ...((current.guacamoleConfig as Record) ?? {}), - [key]: value, - }, - })); +function SectionNotes({ protocol }: { protocol: Protocol }) { + const { t } = useTranslation(); + return ( + } + > +
+ {protocol === "rdp" &&

{t("hosts.userDefaultsNote")}

} + {isElectron() &&

{t("hosts.connectionOriginNote")}

} +
+
+ ); } function RdpSection(props: HostEditorSectionProps) { - const { t } = useTranslation(); - const setGuacField = useGuacFieldSetter(props.updateForm); + const { form, setField, setGuacField } = remoteDesktopForm(props); return ( <> - } - > - - - props.setField("inheritRemoteDesktopDefaults", value) - } - /> - - + ); } function VncSection(props: HostEditorSectionProps) { - const setGuacField = useGuacFieldSetter(props.updateForm); + const { form, setField, setGuacField } = remoteDesktopForm(props); return ( - + <> + + + ); } function TelnetSection(props: HostEditorSectionProps) { - const setGuacField = useGuacFieldSetter(props.updateForm); + const { form, setField, setGuacField } = remoteDesktopForm(props); return ( - + <> + + + ); } @@ -193,23 +199,96 @@ const SECTIONS: Record> = { telnet: TelnetSection, }; -async function openNativeRdp(host: Record): Promise { - const t = (key: string) => i18next.t(`remote-desktop:${key}`); - try { - const result = await window.electronAPI.openNativeRdp({ - host: String(host.ip), - port: (host.rdpPort as number | undefined) ?? 3389, - username: host.rdpUser as string | undefined, - domain: host.domain as string | undefined, +function registerNativeRdp(app: TermixApp): void { + let disposed = false; + app.onDispose(() => { + disposed = true; + }); + void nativeRdpAvailable().then((available) => { + if (disposed || !available) return; + app.registerHostAction({ + id: "rdp-native", + titleKey: "hosts.openNativeRdp", + icon: MonitorUp, + kind: "open", + order: 105, + when: (host) => protocolEnabled(host, "rdp"), + run: (host) => { + const record = host as unknown as RemoteHostLogin; + void openNativeRdp({ + host: String(record.ip ?? ""), + port: hostRemoteOptions(record).rdpPort, + username: record.rdpUser, + domain: record.domain, + }) + .then((result) => { + if (result.success) toast.success(app.t("hosts.nativeRdpOpened")); + else toast.error(result.error || app.t("hosts.nativeRdpFailed")); + }) + .catch(() => toast.error(app.t("hosts.nativeRdpFailed"))); + }, + }); + }); +} + +function registerHostSurfaces(app: TermixApp): void { + for (const protocol of PROTOCOLS) { + const when = (host: PluginHostRecord) => protocolEnabled(host, protocol.id); + + app.registerHostProtocol({ + id: protocol.id, + settingKey: ENABLE_KEY[protocol.id], + portKey: PORT_KEY[protocol.id], + defaultPort: DEFAULT_PORT[protocol.id], + titleKey: protocol.titleKey, + descriptionKey: protocol.descriptionKey, + icon: protocol.icon, + order: protocol.order, + quickConnect: protocol.quickConnect, + }); + + app.registerHostAction({ + id: protocol.id, + titleKey: protocol.titleKey, + icon: protocol.icon, + kind: "connect", + priority: protocol.priority, + order: protocol.order, + tabType: protocol.id, + copyUrlView: protocol.id, + when, + }); + + app.registerPaletteEntry({ + id: `remote-desktop.${protocol.id}`, + titleKey: protocol.paletteKey, + icon: protocol.icon, + keywords: [protocol.id], + scope: "host", + when: (host) => !!host && when(host), + run: (shell, host) => { + if (host) shell.openTab(host, protocol.id); + }, + }); + + app.registerHostEditorSection({ + id: protocol.id, + group: "top", + titleKey: protocol.titleKey, + icon: protocol.icon, + order: protocol.order / 5, + visible: (protocols) => !!protocols[ENABLE_KEY[protocol.id]], + component: SECTIONS[protocol.id], }); - if (result.success) toast.success(t("hosts.nativeRdpOpened")); - else toast.error(result.error || t("hosts.nativeRdpFailed")); - } catch { - toast.error(t("hosts.nativeRdpFailed")); } + + if (isElectron()) registerNativeRdp(app); } export function activate(app: TermixApp): void { + setRemoteDesktopApp(app); + app.onDispose(() => setRemoteDesktopApp(null)); + // Guest pages only draw shared streams. app.registerSlotContribution("session.remoteDisplay", { actionId: "remote-desktop.display", @@ -253,57 +332,24 @@ export function activate(app: TermixApp): void { noHostMessageKey: "remoteDesktop.noHostSelected", persistent: true, session: true, - restore: (host) => !!host[protocol.enableKey], + restore: (host) => protocolEnabled(host, protocol.id), activityTypes: [protocol.id], standalone: RemoteDesktopStandalone, preload: () => import("./GuacamoleApp"), }); - - app.registerHostAction({ - id: protocol.id, - titleKey: protocol.titleKey, - icon: protocol.icon, - kind: "connect", - priority: protocol.priority, - order: protocol.order, - tabType: protocol.id, - copyUrlView: protocol.id, - when: (host) => !!host[protocol.enableKey], - }); - - app.registerHostEditorSection({ - id: protocol.id, - group: "top", - titleKey: protocol.titleKey, - icon: protocol.icon, - order: protocol.order / 5, - visible: (protocols) => !!protocols[protocol.enableKey], - component: SECTIONS[protocol.id], - }); } - // The desktop app on Windows can hand RDP to the system client instead. - if (window.electronAPI?.isElectron) { - let disposed = false; - app.onDispose(() => { - disposed = true; + // An admin can turn Remote Desktop off; its ways in go with it. + let disposed = false; + app.onDispose(() => { + disposed = true; + }); + void getGuacdStatus("local", { probe: false }) + .then((status) => status.enabled !== false) + .catch(() => true) + .then((enabled) => { + if (!disposed && enabled) registerHostSurfaces(app); }); - window.electronAPI - .getPlatform() - .then((platform: string) => { - if (disposed || platform !== "win32") return; - app.registerHostAction({ - id: "rdp-native", - titleKey: "hosts.openNativeRdp", - icon: MonitorUp, - kind: "open", - order: 105, - when: (host) => !!host.enableRdp, - run: (host) => void openNativeRdp(host), - }); - }) - .catch(() => {}); - } app.registerSlotContribution("onboarding.features", { actionId: "remote-desktop.feature", diff --git a/plugins/remote-desktop/src/frontend/quick-connect-guac-host.ts b/plugins/remote-desktop/src/frontend/quick-connect-guac-host.ts index 488d9c8f5..96949b4fd 100644 --- a/plugins/remote-desktop/src/frontend/quick-connect-guac-host.ts +++ b/plugins/remote-desktop/src/frontend/quick-connect-guac-host.ts @@ -1,19 +1,24 @@ -import type { Host } from "@/types/ui-types"; import type { GuacamoleQuickHost } from "./GuacamoleApp"; +import { + hostRemoteOptions, + type Protocol, + type RemoteHostLogin, +} from "./host-remote"; /** The slice of a quick-connect host that GuacamoleApp mints a token from. */ -export function quickConnectGuacHost(host: Host): GuacamoleQuickHost { +export function quickConnectGuacHost( + host: RemoteHostLogin, +): GuacamoleQuickHost { + const options = hostRemoteOptions(host); + const connectionType: Protocol = options.enableVnc ? "vnc" : "rdp"; return { - name: host.name, - ip: host.ip, - connectionType: host.enableVnc ? "vnc" : "rdp", + name: host.name ?? undefined, + ip: host.ip ?? "", + connectionType, domain: host.domain, - rdpPort: host.rdpPort, - vncPort: host.vncPort, + port: connectionType === "vnc" ? options.vncPort : options.rdpPort, rdpAuthType: host.rdpAuthType, - rdpUser: host.rdpUser, - rdpPassword: host.rdpPassword, - vncUser: host.vncUser, - vncPassword: host.vncPassword, + username: connectionType === "vnc" ? host.vncUser : host.rdpUser, + password: connectionType === "vnc" ? host.vncPassword : host.rdpPassword, }; } diff --git a/plugins/remote-desktop/src/frontend/rdp-credential-prompt.ts b/plugins/remote-desktop/src/frontend/rdp-credential-prompt.ts index 746e5ffab..80eb154e7 100644 --- a/plugins/remote-desktop/src/frontend/rdp-credential-prompt.ts +++ b/plugins/remote-desktop/src/frontend/rdp-credential-prompt.ts @@ -1,4 +1,4 @@ -import type { HostAuthOverrides } from "@/types/auth-protocols"; +import type { RemoteHostLogin } from "./host-remote"; export function needsRdpCredentialPrompt({ protocol, @@ -7,7 +7,7 @@ export function needsRdpCredentialPrompt({ }: { protocol: "rdp" | "vnc" | "telnet"; rdpAuthType?: string; - authOverrides?: HostAuthOverrides; + authOverrides?: RemoteHostLogin["authOverrides"]; }): boolean { return ( protocol === "rdp" && diff --git a/plugins/remote-desktop/src/frontend/remote-form.ts b/plugins/remote-desktop/src/frontend/remote-form.ts new file mode 100644 index 000000000..96e833041 --- /dev/null +++ b/plugins/remote-desktop/src/frontend/remote-form.ts @@ -0,0 +1,100 @@ +import type { HostEditorSectionProps } from "@termix/plugin-sdk/frontend"; +import { PLUGIN_ID, remoteOptions } from "./host-remote"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * The host editor fields the RDP, VNC and Telnet tabs edit. The logins are + * core host fields; the options (ports, security, guacd settings) are this + * plugin's host settings, presented here under the names the tabs use. + */ +export interface RemoteDesktopForm { + domain: string; + security: string; + ignoreCert: boolean; + rdpPort: number; + vncPort: number; + telnetPort: number; + guacamoleConfig: Record; + rdpAuthType: "direct" | "credential" | "none"; + rdpCredentialId: string; + rdpUser: string; + rdpPassword: string; + vncAuthType: "direct" | "credential"; + vncCredentialId: string; + vncUser: string; + vncPassword: string; + telnetAuthType: "direct" | "credential"; + telnetCredentialId: string; + telnetUser: string; + telnetPassword: string; + statsConfig: Record; +} + +export type RemoteFormSetField = ( + key: K, + value: RemoteDesktopForm[K], +) => void; + +/** Tab field name to this plugin's host setting key. */ +const SETTING_FOR_FIELD: Partial> = { + rdpPort: "rdpPort", + vncPort: "vncPort", + telnetPort: "telnetPort", + security: "rdpSecurity", + ignoreCert: "rdpIgnoreCert", +}; + +type PluginSettingsBag = Record>; + +function patchSettings( + current: Record, + patch: (settings: Record) => Record, +): Record { + const bag = (current.pluginSettings as PluginSettingsBag | undefined) ?? {}; + const mine = bag[PLUGIN_ID] ?? {}; + return { + pluginSettings: { ...bag, [PLUGIN_ID]: { ...mine, ...patch(mine) } }, + }; +} + +export function remoteDesktopForm(props: HostEditorSectionProps): { + form: RemoteDesktopForm; + setField: RemoteFormSetField; + setGuacField: (key: string, value: unknown) => void; +} { + const bag = props.form?.pluginSettings as PluginSettingsBag | undefined; + const options = remoteOptions(bag?.[PLUGIN_ID]); + const form: RemoteDesktopForm = { + ...(props.form as RemoteDesktopForm), + rdpPort: options.rdpPort, + vncPort: options.vncPort, + telnetPort: options.telnetPort, + security: options.rdpSecurity, + ignoreCert: options.rdpIgnoreCert, + guacamoleConfig: options.guacamoleConfig as Record, + }; + + const setField: RemoteFormSetField = (key, value) => { + const setting = SETTING_FOR_FIELD[key]; + if (!setting) { + props.setField(key, value); + return; + } + props.updateForm((current) => + patchSettings(current, () => ({ [setting]: value })), + ); + }; + + const setGuacField = (key: string, value: unknown) => + props.updateForm((current) => + patchSettings(current, (mine) => ({ + guacamoleConfig: { + ...remoteOptions(mine).guacamoleConfig, + [key]: value, + }, + })), + ); + + return { form, setField, setGuacField }; +} diff --git a/plugins/remote-desktop/tests/backend/host-settings.test.ts b/plugins/remote-desktop/tests/backend/host-settings.test.ts new file mode 100644 index 000000000..79bf6ef25 --- /dev/null +++ b/plugins/remote-desktop/tests/backend/host-settings.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from "vitest"; +import { + normalizeImportedHost, + userDefaultParams, +} from "../../src/backend/host-settings.js"; + +describe("normalizeImportedHost", () => { + it("reads a Termix export's pluginSettings", () => { + expect( + normalizeImportedHost({ + pluginSettings: { + "remote-desktop": { + enableVnc: true, + vncPort: 5901, + guacamoleConfig: { cursor: "local" }, + }, + }, + }), + ).toEqual({ + enableVnc: true, + vncPort: 5901, + guacamoleConfig: { cursor: "local" }, + }); + }); + + it("reads the flat fields hosts had before 2.9.0", () => { + expect( + normalizeImportedHost({ + enableRdp: true, + rdpPort: "3390", + security: "nla", + ignoreCert: 1, + guacamoleConfig: JSON.stringify({ dpi: 120 }), + enableTerminalToolbar: false, + }), + ).toEqual({ + enableRdp: true, + rdpPort: 3390, + rdpSecurity: "nla", + rdpIgnoreCert: true, + guacamoleConfig: { dpi: 120 }, + enableToolbar: false, + }); + }); + + it("turns a connectionType-only host into its switch and port", () => { + expect( + normalizeImportedHost({ connectionType: "telnet", port: 2323 }), + ).toEqual({ enableTelnet: true, telnetPort: 2323 }); + }); + + it("writes nothing for a plain SSH row", () => { + expect( + normalizeImportedHost({ connectionType: "ssh", port: 22 }), + ).toBeNull(); + }); + + it("drops a port out of range", () => { + expect(normalizeImportedHost({ enableRdp: true, rdpPort: 70000 })).toEqual({ + enableRdp: true, + }); + }); +}); + +describe("userDefaultParams", () => { + it("maps the user settings onto guacd parameters, skipping inherit", () => { + expect( + userDefaultParams({ + colorDepth: "24", + resizeMethod: "reconnect", + disableCopy: "on", + enableWallpaper: "off", + enableDrive: "inherit", + }), + ).toEqual({ + "color-depth": 24, + "resize-method": "reconnect", + "disable-copy": true, + "enable-wallpaper": false, + }); + }); +}); diff --git a/plugins/remote-desktop/tests/backend/rdp-settings.test.ts b/plugins/remote-desktop/tests/backend/rdp-settings.test.ts index 10d94d61a..2738c78d9 100644 --- a/plugins/remote-desktop/tests/backend/rdp-settings.test.ts +++ b/plugins/remote-desktop/tests/backend/rdp-settings.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from "vitest"; import { buildRdpSettings, - resolveRdpAuthTypeForConnect, resolveRdpDomain, } from "../../src/backend/rdp-settings.js"; @@ -53,32 +52,3 @@ describe("buildRdpSettings", () => { expect(resolveRdpDomain("none", undefined, "SAVED")).toBe("SAVED"); }); }); - -describe("resolveRdpAuthTypeForConnect", () => { - it("keeps prompt-on-connect authentication for a secretless recipient", () => { - expect( - resolveRdpAuthTypeForConnect({ - storedAuthType: "none", - sharedResolution: { source: "secretless" }, - }), - ).toBe("none"); - }); - - it("uses a recipient override instead of prompting", () => { - expect( - resolveRdpAuthTypeForConnect({ - storedAuthType: "none", - sharedResolution: { source: "personal-override" }, - }), - ).toBe("credential"); - }); - - it("uses authentication shared by the owner", () => { - expect( - resolveRdpAuthTypeForConnect({ - storedAuthType: "credential", - sharedResolution: { source: "owner-shared", authType: "credential" }, - }), - ).toBe("credential"); - }); -}); diff --git a/plugins/remote-desktop/tests/backend/routes.test.ts b/plugins/remote-desktop/tests/backend/routes.test.ts new file mode 100644 index 000000000..278e8eb04 --- /dev/null +++ b/plugins/remote-desktop/tests/backend/routes.test.ts @@ -0,0 +1,318 @@ +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import express, { type Router } from "express"; +import { afterEach, describe, expect, it } from "vitest"; +import { + createMockCtx, + type MockPluginContext, +} from "@termix/plugin-sdk/testing"; +import type { + PluginContext, + PluginProtocolTarget, +} from "@termix/plugin-sdk/backend"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import manifestJson from "../../manifest.json"; +import { activate } from "../../src/backend/index.js"; +import { GuacamoleTokenService } from "../../src/backend/token-service.js"; + +const manifest = manifestJson as unknown as PluginManifest; + +// The token key comes from JWT_SECRET; without one each service picks its own. +process.env.JWT_SECRET = "remote-desktop-test-secret"; + +function target( + overrides: Partial = {}, +): PluginProtocolTarget { + return { + host: { + id: 7, + name: "Win box", + ip: "10.0.0.7", + port: 22, + ownerUserId: "user-1", + jumpHosts: [], + }, + shared: false, + auth: { + authType: "direct", + username: "admin", + password: "secret", + domain: "CORP", + ...overrides, + }, + }; +} + +interface Server { + mock: MockPluginContext; + request: ( + method: string, + path: string, + body?: unknown, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) => Promise<{ status: number; body: any }>; + close: () => Promise; +} + +let server: Server | null = null; + +afterEach(async () => { + await server?.close(); + server = null; +}); + +async function start( + options: { + protocolTargets?: Record; + services?: Record; + capabilities?: string[]; + } = {}, +): Promise { + let router: Router | null = null; + const mock = createMockCtx({ + pluginId: manifest.id, + manifest, + capabilities: options.capabilities ?? manifest.capabilities, + router: () => (router = express.Router()), + protocolTargets: options.protocolTargets, + services: options.services, + }); + await activate(mock.ctx as PluginContext); + + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + mock.setActor(req.header("x-test-user") ?? "user-1"); + next(); + }); + app.use((req, res, next) => router!(req, res, next)); + const httpServer = http.createServer(app); + await new Promise((resolve) => httpServer.listen(0, resolve)); + const { port } = httpServer.address() as AddressInfo; + + return { + mock, + async request(method, path, body) { + const response = await fetch(`http://127.0.0.1:${port}${path}`, { + method, + headers: body ? { "content-type": "application/json" } : {}, + body: body ? JSON.stringify(body) : undefined, + }); + const text = await response.text(); + return { status: response.status, body: text ? JSON.parse(text) : null }; + }, + async close() { + await new Promise((resolve) => httpServer.close(() => resolve())); + for (const dispose of [...mock.disposals].reverse()) await dispose(); + }, + }; +} + +const tokens = new GuacamoleTokenService(); + +describe("POST /connect-host/:hostId", () => { + it("mints a token from the resolved login and the host's own settings", async () => { + server = await start({ protocolTargets: { "7:rdp": target() } }); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + await server.mock.ctx.settings.setHost(7, "rdpPort", 3390); + await server.mock.ctx.settings.setHost(7, "rdpSecurity", "nla"); + await server.mock.ctx.settings.setHost(7, "guacamoleConfig", { + "color-depth": 16, + "enable-wallpaper": "auto", + }); + + const response = await server.request("POST", "/connect-host/7", { + protocol: "rdp", + }); + + expect(response.status).toBe(200); + expect(response.body.termixConnectId).toEqual(expect.any(String)); + const token = tokens.decryptToken(response.body.token); + expect(token?.connection.settings).toMatchObject({ + hostname: "10.0.0.7", + port: 3390, + username: "admin", + password: "secret", + domain: "CORP", + security: "nla", + "color-depth": 16, + }); + expect(token?.connection.settings).not.toHaveProperty("enable-wallpaper"); + expect(token?.termixMeta).toMatchObject({ + hostId: 7, + hostName: "Win box", + protocol: "rdp", + }); + expect(server.mock.audits.at(-1)).toMatchObject({ + action: "rdp_connect", + success: true, + }); + }); + + it("puts the user's RDP defaults under the host's own values", async () => { + server = await start({ protocolTargets: { "7:rdp": target() } }); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + await server.mock.ctx.settings.setHost(7, "guacamoleConfig", { + "color-depth": 32, + }); + await server.mock.ctx.settings.setUser("user-1", "colorDepth", "16"); + await server.mock.ctx.settings.setUser("user-1", "disableCopy", "on"); + + const response = await server.request("POST", "/connect-host/7", { + protocol: "rdp", + }); + const settings = tokens.decryptToken(response.body.token)?.connection + .settings; + expect(settings?.["color-depth"]).toBe(32); + expect(settings?.["disable-copy"]).toBe(true); + }); + + it("uses the prompted login when the host asks at connect time", async () => { + server = await start({ + protocolTargets: { "7:rdp": target({ authType: "none", username: "" }) }, + }); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + + const response = await server.request("POST", "/connect-host/7", { + protocol: "rdp", + promptedUsername: "bob", + promptedPassword: "pw", + promptedDomain: "", + }); + const settings = tokens.decryptToken(response.body.token)?.connection + .settings; + expect(settings).toMatchObject({ username: "bob", password: "pw" }); + }); + + it("refuses a protocol the host has switched off", async () => { + server = await start({ protocolTargets: { "7:vnc": target() } }); + const response = await server.request("POST", "/connect-host/7", { + protocol: "vnc", + }); + expect(response.status).toBe(400); + }); + + it("answers 404 when the caller cannot reach the host", async () => { + server = await start(); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + const response = await server.request("POST", "/connect-host/7", { + protocol: "rdp", + }); + expect(response.status).toBe(404); + }); + + it("refuses everything while an admin has turned it off", async () => { + server = await start({ protocolTargets: { "7:rdp": target() } }); + await server.mock.ctx.settings.set("enabled", false); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + + expect( + (await server.request("POST", "/connect-host/7", { protocol: "rdp" })) + .status, + ).toBe(403); + expect( + ( + await server.request("POST", "/token", { + type: "rdp", + hostname: "10.0.0.9", + }) + ).status, + ).toBe(403); + const status = await server.request("GET", "/status?probe=0"); + expect(status.body.enabled).toBe(false); + }); + + it("records only when session recording is on for the host", async () => { + const enabledFor = async (hostId: number) => hostId === 7; + server = await start({ + protocolTargets: { "7:rdp": target() }, + services: { + "recordings.writer": { + enabledFor, + createFinished: async () => ({ id: 1 }), + }, + }, + }); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + const response = await server.request("POST", "/connect-host/7", { + protocol: "rdp", + }); + const token = tokens.decryptToken(response.body.token); + expect(token?.recording).toMatchObject({ hostId: 7, protocol: "rdp" }); + expect(token?.connection.settings["recording-name"]).toEqual( + expect.any(String), + ); + }); +}); + +describe("the rest of the routes", () => { + it("mints a quick connect token with the caller's defaults", async () => { + server = await start(); + await server.mock.ctx.settings.setUser("user-1", "colorDepth", "24"); + const response = await server.request("POST", "/token", { + type: "rdp", + hostname: "10.0.0.9", + username: "u", + password: "p", + }); + expect(response.status).toBe(200); + expect( + tokens.decryptToken(response.body.token)?.connection.settings, + ).toMatchObject({ hostname: "10.0.0.9", port: 3389, "color-depth": 24 }); + }); + + it("offers native RDP only in the desktop app", async () => { + server = await start(); + expect((await server.request("GET", "/native-rdp")).body).toEqual({ + available: false, + }); + expect( + (await server.request("POST", "/native-rdp", { host: "10.0.0.7" })) + .status, + ).toBe(404); + }); + + it("does not hand out another user's session id", async () => { + server = await start(); + const response = await server.request("GET", "/connection/unknown"); + expect(response.body).toEqual({ guacamoleConnectionId: null }); + }); +}); + +describe("activate", () => { + it("fails closed without network:serve", async () => { + await expect( + start({ + capabilities: manifest.capabilities.filter( + (capability) => capability !== "network:serve", + ), + }), + ).rejects.toThrow(/network:serve/); + }); + + it("cannot read a host's login without credentials:read", async () => { + server = await start({ + protocolTargets: { "7:rdp": target() }, + capabilities: manifest.capabilities.filter( + (capability) => capability !== "credentials:read", + ), + }); + await server.mock.ctx.settings.setHost(7, "enableRdp", true); + const response = await server.request("POST", "/connect-host/7", { + protocol: "rdp", + }); + expect(response.status).toBe(500); + expect(server.mock.checked).toContain("credentials:read"); + }); + + it("provides sessions.live for rdp, vnc and telnet", async () => { + server = await start(); + expect([...server.mock.services.keys()].sort()).toEqual( + expect.arrayContaining([ + "sessions.live#rdp", + "sessions.live#telnet", + "sessions.live#vnc", + ]), + ); + }); +}); diff --git a/plugins/remote-desktop/tests/backend/sessions.test.ts b/plugins/remote-desktop/tests/backend/sessions.test.ts new file mode 100644 index 000000000..2fb9ea0f0 --- /dev/null +++ b/plugins/remote-desktop/tests/backend/sessions.test.ts @@ -0,0 +1,106 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { RemoteSessions } from "../../src/backend/sessions.js"; +import { createLiveSessions } from "../../src/backend/live-sessions.js"; +import { GuacamoleTokenService } from "../../src/backend/token-service.js"; + +const meta = { + termixConnectId: "c1", + hostId: 7, + hostName: "Win box", + ownerUserId: "user-1", + protocol: "rdp" as const, + tabInstanceId: "tab-1", +}; + +afterEach(() => { + vi.useRealTimers(); +}); + +describe("RemoteSessions", () => { + it("moves parked resources onto the session and releases them on close", () => { + const sessions = new RemoteSessions(); + const tunnel = vi.fn(); + const tracking = vi.fn(); + sessions.park("c1", [tunnel]); + sessions.opened(meta, "g1", [tracking]); + + expect(sessions.byConnect("c1")?.guacamoleConnectionId).toBe("g1"); + expect(sessions.byGuacamole("g1")?.hostName).toBe("Win box"); + expect(tunnel).not.toHaveBeenCalled(); + + sessions.closed("c1"); + expect(tunnel).toHaveBeenCalledOnce(); + expect(tracking).toHaveBeenCalledOnce(); + expect(sessions.byGuacamole("g1")).toBeNull(); + }); + + it("releases a tunnel guacd never used", () => { + vi.useFakeTimers(); + const sessions = new RemoteSessions(1000); + const tunnel = vi.fn(); + sessions.park("c1", [tunnel]); + vi.advanceTimersByTime(1000); + expect(tunnel).toHaveBeenCalledOnce(); + }); + + it("releases everything on clear, and one failing close does not stop the rest", () => { + const sessions = new RemoteSessions(); + const failing = vi.fn(() => { + throw new Error("already closed"); + }); + const pending = vi.fn(); + const open = vi.fn(); + sessions.park("c2", [failing, pending]); + sessions.park("c1", [open]); + sessions.opened(meta, "g1"); + sessions.clear(); + expect(pending).toHaveBeenCalledOnce(); + expect(open).toHaveBeenCalledOnce(); + }); +}); + +describe("sessions.live providers", () => { + const tokens = new GuacamoleTokenService(); + + it("describes a live session of its own protocol only", () => { + const sessions = new RemoteSessions(); + sessions.opened(meta, "g1"); + const deps = { sessions, tokens, endSession: vi.fn(() => true) }; + const rdp = createLiveSessions("rdp", deps); + const vnc = createLiveSessions("vnc", deps); + + expect(rdp.getSession("g1")).toMatchObject({ + id: "g1", + userId: "user-1", + hostId: 7, + hostName: "Win box", + isConnected: true, + tabInstanceId: "tab-1", + }); + expect(vnc.getSession("g1")).toBeNull(); + }); + + it("mints a join token for a viewer and refuses an unknown session", () => { + const sessions = new RemoteSessions(); + sessions.opened(meta, "g1"); + const rdp = createLiveSessions("rdp", { + sessions, + tokens, + endSession: vi.fn(() => true), + }); + + const token = tokens.decryptToken(rdp.createViewerToken("g1", true)); + expect(token?.connection).toMatchObject({ join: "g1", readOnly: true }); + expect(token?.recording).toBeUndefined(); + expect(() => rdp.createViewerToken("nope", false)).toThrow(); + }); + + it("ends the session for its owner", () => { + const sessions = new RemoteSessions(); + sessions.opened(meta, "g1"); + const endSession = vi.fn(() => true); + const rdp = createLiveSessions("rdp", { sessions, tokens, endSession }); + rdp.ownerEndSession("g1", "owner ended"); + expect(endSession).toHaveBeenCalledWith("g1"); + }); +}); diff --git a/plugins/remote-desktop/tests/backend/token-delivery.test.ts b/plugins/remote-desktop/tests/backend/token-delivery.test.ts deleted file mode 100644 index afa75a428..000000000 --- a/plugins/remote-desktop/tests/backend/token-delivery.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { readFileSync } from "node:fs"; -import { runInNewContext } from "node:vm"; -import ts from "typescript"; -import { expect, it, vi } from "vitest"; - -it("returns the minted token without waiting for a connection that needs that token", async () => { - const source = readFileSync( - new URL("../../src/backend/routes.ts", import.meta.url), - "utf8", - ); - const ast = ts.createSourceFile( - "routes.ts", - source, - ts.ScriptTarget.Latest, - true, - ); - const route = ast.statements.find( - (s) => - ts.isExpressionStatement(s) && - ts.isCallExpression(s.expression) && - s.expression.arguments[0]?.getText(ast) === '"/connect-host/:hostId"', - ) as ts.ExpressionStatement; - const callback = (route.expression as ts.CallExpression) - .arguments[1] as ts.ArrowFunction; - const block = (callback.body as ts.Block).statements[0] as ts.TryStatement; - const index = block.tryBlock.statements - .map((s, i) => (ts.isSwitchStatement(s) ? i : -1)) - .filter((i) => i >= 0) - .at(-1)!; - expect(index).toBeGreaterThan(-1); - const tail = block.tryBlock.statements - .slice(index + 1) - .map((s) => s.getText(ast)) - .join("\n"); - const json = vi.fn(); - const wait = vi.fn(() => { - throw new Error("Client has not received the token yet"); - }); - await runInNewContext( - ts.transpileModule(`(async () => { ${tail} })()`, { - compilerOptions: { target: ts.ScriptTarget.ES2022 }, - }).outputText, - { - waitForGuacdOpen: wait, - token: "minted-token", - termixConnectId: "correlation-id", - req: {}, - getRequestMeta: () => ({}), - logAudit: async () => {}, - getAuditUsername: async () => "owner", - userId: "owner", - connectionType: "rdp", - hostId: 7, - hostname: "host", - port: 3389, - res: { json }, - }, - ); - expect(wait).not.toHaveBeenCalled(); - expect(json).toHaveBeenCalledWith({ - token: "minted-token", - termixConnectId: "correlation-id", - guacamoleConnectionId: null, - }); -}); - -it("only reveals the live session ID to its owner", () => { - const source = readFileSync( - new URL("../../src/backend/guacamole-server.ts", import.meta.url), - "utf8", - ); - const ast = ts.createSourceFile( - "server.ts", - source, - ts.ScriptTarget.Latest, - true, - ); - const fn = ast.statements.find( - (s) => - ts.isFunctionDeclaration(s) && - s.name?.text === "getGuacSessionByConnectId", - )!; - const info = { ownerUserId: "owner", guacamoleConnectionId: "guacd-id" }; - const context = { - exports: {} as Record unknown>, - guacSessionByConnectId: new Map([["connect-id", info]]), - }; - runInNewContext( - ts.transpileModule(fn.getText(ast), { - compilerOptions: { module: ts.ModuleKind.CommonJS }, - }).outputText, - context, - ); - const lookup = context.exports.getGuacSessionByConnectId; - expect(lookup("connect-id", "owner")).toEqual(info); - expect(lookup("connect-id", "other")).toBeNull(); - expect(lookup("pending", "owner")).toBeNull(); -}); diff --git a/plugins/remote-desktop/tests/backend/token-service.test.ts b/plugins/remote-desktop/tests/backend/token-service.test.ts index 9646a6898..0a0e0c1fb 100644 --- a/plugins/remote-desktop/tests/backend/token-service.test.ts +++ b/plugins/remote-desktop/tests/backend/token-service.test.ts @@ -1,20 +1,9 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; -vi.mock("../../../../src/backend/utils/logger.js", () => ({ - guacLogger: { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - success: vi.fn(), - }, -})); - -const { GuacamoleTokenService } = - await import("../../src/backend/token-service.js"); +import { GuacamoleTokenService } from "../../src/backend/token-service.js"; describe("GuacamoleTokenService", () => { - const tokenService = GuacamoleTokenService.getInstance(); + const tokenService = new GuacamoleTokenService(); it("disables RDP pre-authentication when no credentials are configured", () => { const token = tokenService.createRdpToken("windows.example.test", "", ""); diff --git a/plugins/remote-desktop/tests/frontend/activate.test.tsx b/plugins/remote-desktop/tests/frontend/activate.test.tsx index 814097280..b9b00717c 100644 --- a/plugins/remote-desktop/tests/frontend/activate.test.tsx +++ b/plugins/remote-desktop/tests/frontend/activate.test.tsx @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { renderWithApp, type RenderedPluginApp, @@ -45,9 +45,39 @@ describe(`${manifest.id} activate`, () => { expect(app.registered.panels()).toEqual([]); expect(app.registered.railItems()).toEqual([]); expect(app.registered.hostActions()).toEqual([]); + expect(app.registered.hostProtocols()).toEqual([]); expect(app.registered.hostEditorSections()).toEqual([]); expect(app.registered.dashboardCards()).toEqual([]); expect(app.registered.settingsComponents()).toEqual([]); expect(app.registered.actions()).toEqual([]); }); + + it("adds RDP, VNC and Telnet as host protocols while turned on", async () => { + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: { get: async () => ({ data: { enabled: true } }) } as never, + }); + await vi.waitFor(() => + expect(rendered!.registered.hostProtocols().sort()).toEqual([ + "rdp", + "telnet", + "vnc", + ]), + ); + expect(rendered.registered.hostActions().map((a) => a.id)).toEqual( + expect.arrayContaining(["rdp", "vnc", "telnet"]), + ); + }); + + it("offers no way in while an admin has turned it off", async () => { + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: { get: async () => ({ data: { enabled: false } }) } as never, + }); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(rendered.registered.hostProtocols()).toEqual([]); + expect(rendered.registered.hostActions()).toEqual([]); + }); }); diff --git a/plugins/remote-desktop/tests/frontend/guacamole-api.test.ts b/plugins/remote-desktop/tests/frontend/guacamole-api.test.ts index 73b125d60..eaf8d1004 100644 --- a/plugins/remote-desktop/tests/frontend/guacamole-api.test.ts +++ b/plugins/remote-desktop/tests/frontend/guacamole-api.test.ts @@ -11,22 +11,27 @@ const remoteApiMock = vi.hoisted(() => ({ const isElectronMock = vi.hoisted(() => vi.fn(() => false)); const resolveRemoteHostIdMock = vi.hoisted(() => vi.fn()); -vi.mock("@/main-axios", () => ({ - authApi: authApiMock, - getRemoteGuacamoleApi: () => remoteApiMock, +vi.mock("@termix/plugin-sdk/ui", () => ({ isElectron: isElectronMock, - handleApiError: (error: unknown) => error, -})); -vi.mock("@/lib/remote-server-api", () => ({ resolveRemoteHostId: resolveRemoteHostIdMock, })); import { + setRemoteDesktopApp, getGuacdStatus, getGuacamoleConnectionId, getGuacamoleTokenFromHost, } from "../../src/frontend/guacamole-api"; +// Mirrors app.apiFor: the remote server only for a remote origin in the +// desktop app, the ordinary client otherwise. +setRemoteDesktopApp({ + apiFor: (origin) => + (origin === "remote" && isElectronMock() + ? remoteApiMock + : authApiMock) as never, +}); + beforeEach(() => { authApiMock.get.mockClear(); authApiMock.post.mockClear(); @@ -42,9 +47,7 @@ describe("guacamole API origin", () => { await getGuacdStatus("remote"); await getGuacamoleTokenFromHost(9, "remote", "vnc"); - expect(authApiMock.get).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/status", - ); + expect(authApiMock.get).toHaveBeenCalledWith("/status"); expect(authApiMock.post).toHaveBeenCalledOnce(); expect(remoteApiMock.get).not.toHaveBeenCalled(); expect(remoteApiMock.post).not.toHaveBeenCalled(); @@ -60,9 +63,7 @@ describe("guacamole API origin", () => { expect(status.guacd.status).toBe("connected"); expect(token.token).toBe("remote-token"); - expect(remoteApiMock.get).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/status", - ); + expect(remoteApiMock.get).toHaveBeenCalledWith("/status"); expect(remoteApiMock.post).toHaveBeenCalledOnce(); expect(authApiMock.get).not.toHaveBeenCalled(); expect(authApiMock.post).not.toHaveBeenCalled(); @@ -84,15 +85,12 @@ describe("guacamole API origin", () => { "host-sync-id", ); - expect(remoteApiMock.post).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/connect-host/41", - { - protocol: "rdp", - promptedUsername: "admin", - promptedPassword: "secret", - promptedDomain: "EXAMPLE", - }, - ); + expect(remoteApiMock.post).toHaveBeenCalledWith("/connect-host/41", { + protocol: "rdp", + promptedUsername: "admin", + promptedPassword: "secret", + promptedDomain: "EXAMPLE", + }); expect(resolveRemoteHostIdMock).toHaveBeenCalledWith("host-sync-id"); }); @@ -105,15 +103,12 @@ describe("guacamole API origin", () => { domain: "", }); - expect(authApiMock.post).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/connect-host/9", - { - protocol: "rdp", - promptedUsername: "local-admin", - promptedPassword: "secret", - promptedDomain: "", - }, - ); + expect(authApiMock.post).toHaveBeenCalledWith("/connect-host/9", { + protocol: "rdp", + promptedUsername: "local-admin", + promptedPassword: "secret", + promptedDomain: "", + }); }); // Support#1240: a host set to "This Device" is served by the embedded @@ -125,12 +120,9 @@ describe("guacamole API origin", () => { const token = await getGuacamoleTokenFromHost(9, "local", "rdp"); expect(token.token).toBe("local-token"); - expect(authApiMock.post).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/connect-host/9", - { - protocol: "rdp", - }, - ); + expect(authApiMock.post).toHaveBeenCalledWith("/connect-host/9", { + protocol: "rdp", + }); expect(remoteApiMock.post).not.toHaveBeenCalled(); expect(resolveRemoteHostIdMock).not.toHaveBeenCalled(); }); @@ -141,9 +133,7 @@ describe("guacamole API origin", () => { const status = await getGuacdStatus("local"); expect(status.guacd.status).toBe("disconnected"); - expect(authApiMock.get).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/status", - ); + expect(authApiMock.get).toHaveBeenCalledWith("/status"); expect(remoteApiMock.get).not.toHaveBeenCalled(); }); @@ -177,9 +167,8 @@ it("looks up the session on the backend that issued its token", async () => { isElectronMock.mockReturnValue(true); const signal = new AbortController().signal; await getGuacamoleConnectionId("connect/id", "remote", signal); - expect(remoteApiMock.get).toHaveBeenCalledWith( - "/plugin-api/remote-desktop/connection/connect%2Fid", - { signal }, - ); + expect(remoteApiMock.get).toHaveBeenCalledWith("/connection/connect%2Fid", { + signal, + }); expect(authApiMock.get).not.toHaveBeenCalled(); }); diff --git a/plugins/remote-desktop/tests/frontend/quick-connect-guac-host.test.ts b/plugins/remote-desktop/tests/frontend/quick-connect-guac-host.test.ts index 3b3a1189c..e11b87c7c 100644 --- a/plugins/remote-desktop/tests/frontend/quick-connect-guac-host.test.ts +++ b/plugins/remote-desktop/tests/frontend/quick-connect-guac-host.test.ts @@ -1,41 +1,51 @@ import { describe, expect, it } from "vitest"; -import { createQuickConnectHost } from "@/sidebar/quick-connect-host"; import { quickConnectGuacHost } from "../../src/frontend/quick-connect-guac-host"; +// The shape core's Quick Connect builds for a plugin protocol: the switch and +// port in this plugin's host settings, the login on the protocol's fields. +function quickHost(protocol: "rdp" | "vnc", extra: Record) { + return { + id: "quick-connect-1", + name: "admin@10.0.0.2", + ip: "10.0.0.2", + pluginSettings: { + "remote-desktop": { + [protocol === "rdp" ? "enableRdp" : "enableVnc"]: true, + [protocol === "rdp" ? "rdpPort" : "vncPort"]: + protocol === "rdp" ? 3390 : 5901, + }, + }, + ...extra, + }; +} + describe("quickConnectGuacHost", () => { it("carries an RDP quick connect's address and credentials", () => { - const host = createQuickConnectHost({ - ip: "10.0.0.2", - port: 3390, - username: "admin", - authType: "password", - password: "pw", - protocol: "rdp", - domain: "CORP", - }); - expect(quickConnectGuacHost(host)).toMatchObject({ + expect( + quickConnectGuacHost( + quickHost("rdp", { + rdpUser: "admin", + rdpPassword: "pw", + domain: "CORP", + }), + ), + ).toMatchObject({ ip: "10.0.0.2", connectionType: "rdp", - rdpPort: 3390, - rdpUser: "admin", - rdpPassword: "pw", + port: 3390, + username: "admin", + password: "pw", domain: "CORP", }); }); - it("puts a VNC quick connect's password on the VNC fields", () => { - const host = createQuickConnectHost({ - ip: "10.0.0.3", - port: 5901, - username: "", - authType: "password", - password: "vncpw", - protocol: "vnc", - }); - expect(quickConnectGuacHost(host)).toMatchObject({ + it("takes a VNC quick connect's login from the VNC fields", () => { + expect( + quickConnectGuacHost(quickHost("vnc", { vncPassword: "vncpw" })), + ).toMatchObject({ connectionType: "vnc", - vncPort: 5901, - vncPassword: "vncpw", + port: 5901, + password: "vncpw", }); }); }); diff --git a/plugins/remote-desktop/tsconfig.json b/plugins/remote-desktop/tsconfig.json index 1d3e42542..7b35314c3 100644 --- a/plugins/remote-desktop/tsconfig.json +++ b/plugins/remote-desktop/tsconfig.json @@ -1,10 +1,4 @@ { "extends": "@termix/plugin-sdk/tsconfig.plugin.json", - "compilerOptions": { - "paths": { - "@/types/*": ["../../src/types/*"], - "@/*": ["../../src/ui/*"] - } - }, "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"] } diff --git a/plugins/session-recording/manifest.json b/plugins/session-recording/manifest.json index 17ba92920..7563cf623 100644 --- a/plugins/session-recording/manifest.json +++ b/plugins/session-recording/manifest.json @@ -24,7 +24,7 @@ "provides": [ { "service": "recordings.writer", - "version": "1.0.0", + "version": "1.1.0", "permission": "session-recording.view" } ], diff --git a/plugins/session-recording/src/backend/service.ts b/plugins/session-recording/src/backend/service.ts index 713c3cc31..b75002a85 100644 --- a/plugins/session-recording/src/backend/service.ts +++ b/plugins/session-recording/src/backend/service.ts @@ -5,6 +5,7 @@ import type { SessionRecordingRepository } from "./repository.js"; /** * recordings.writer v1, the service ssh-terminal calls to record sessions. + * 1.1 added enabledFor, for a caller that records outside this plugin. * * One call per already-batched chunk (ssh-terminal coalesces on a 300ms * trailing edge, issue #1049): mkdir and writeFile on the first append, @@ -22,6 +23,11 @@ export interface RecordingSink { } export interface RecordingsWriterV1 { + /** + * Whether the host's recording switch is on, for a caller that has to set + * up recording before a session exists (remote desktop tells guacd). + */ + enabledFor: (hostId: number) => Promise; /** Null when recording is off for this user or host. */ open: (meta: { sessionId: string; @@ -53,13 +59,15 @@ export function createRecordingsWriter( ctx: PluginContext, repository: SessionRecordingRepository, ): RecordingsWriterV1 { + const enabledFor = async (hostId: number) => + (await ctx.settings.getHost(hostId, "enableSessionRecording")) !== + false; + return { + enabledFor, + async open(meta) { - const enabled = await ctx.settings.getHost( - meta.hostId, - "enableSessionRecording", - ); - if (enabled === false) return null; + if (!(await enabledFor(meta.hostId))) return null; const dataDir = await ctx.files.dataDir(); const dir = path.join(dataDir, "session_logs", meta.userId); diff --git a/plugins/session-recording/tests/backend/service.test.ts b/plugins/session-recording/tests/backend/service.test.ts index 311bd2e73..094b10ddf 100644 --- a/plugins/session-recording/tests/backend/service.test.ts +++ b/plugins/session-recording/tests/backend/service.test.ts @@ -171,4 +171,12 @@ describe("recordings.writer", () => { expect(row.id).toBeGreaterThan(0); }); + + it("enabledFor follows the host's recording switch", async () => { + const { mock, writer } = await setup(); + await mock.ctx.settings.setHost(7, "enableSessionRecording", true); + await expect(writer.enabledFor(7)).resolves.toBe(true); + await mock.ctx.settings.setHost(7, "enableSessionRecording", false); + await expect(writer.enabledFor(7)).resolves.toBe(false); + }); }); diff --git a/plugins/session-sharing/src/frontend/CollabRoomTab.tsx b/plugins/session-sharing/src/frontend/CollabRoomTab.tsx index 3f18f968e..f236a7df8 100644 --- a/plugins/session-sharing/src/frontend/CollabRoomTab.tsx +++ b/plugins/session-sharing/src/frontend/CollabRoomTab.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { + hostProtocols, useHosts, useTranslation, type PluginHostRecord, @@ -64,12 +65,17 @@ import { type DirectoryRole, } from "./api"; +type SharedProtocol = "ssh" | "rdp" | "vnc" | "telnet"; + +function isSharedProtocol(value: string): value is SharedProtocol { + return ( + value === "ssh" || value === "rdp" || value === "vnc" || value === "telnet" + ); +} + /** A host from the shell's list; the connection fields ride along by name. */ type SSHHostWithStatus = PluginHostRecord & { enableSsh?: boolean; - enableRdp?: boolean; - enableVnc?: boolean; - enableTelnet?: boolean; connectionOrigin?: unknown; }; @@ -350,11 +356,13 @@ export function CollabRoomTab({ } const response = await createRemoteSessionToken( Number(host.id), - await resolveConnectionOrigin({ - connectionType: protocol, - connectionOrigin: host.connectionOrigin as - "local" | "remote" | undefined, - }), + await resolveConnectionOrigin( + { + connectionOrigin: host.connectionOrigin as + "local" | "remote" | undefined, + }, + { defaultRemote: true }, + ), protocol, ); if (!response?.connectionId) { @@ -780,11 +788,9 @@ export function CollabRoomTab({ )} {filteredHosts.map((host) => { - const protocols: Array<"ssh" | "rdp" | "vnc" | "telnet"> = []; - if (host.enableSsh) protocols.push("ssh"); - if (host.enableRdp) protocols.push("rdp"); - if (host.enableVnc) protocols.push("vnc"); - if (host.enableTelnet) protocols.push("telnet"); + const protocols = hostProtocols( + host as unknown as PluginHostRecord, + ).filter(isSharedProtocol); if (protocols.length === 0) return null; return (
( { const origin = isElectron() ? await resolveConnectionOrigin({ - connectionType: "ssh", connectionOrigin: hostConfig.connectionOrigin as "local" | "remote" | null | undefined, }) diff --git a/plugins/web-endpoint/src/frontend/HostEditorWebUiSection.tsx b/plugins/web-endpoint/src/frontend/HostEditorWebUiSection.tsx index 932efad12..6c648cd3c 100644 --- a/plugins/web-endpoint/src/frontend/HostEditorWebUiSection.tsx +++ b/plugins/web-endpoint/src/frontend/HostEditorWebUiSection.tsx @@ -119,7 +119,6 @@ export function HostEditorWebUiSection({ useEffect(() => { let cancelled = false; void resolveConnectionOrigin({ - connectionType: "ssh", connectionOrigin: connectionOrigin as "local" | "remote" | null, }).then((origin) => { if (!cancelled) setOriginIsLocal(origin === "local"); diff --git a/scripts/plugin-boundary-allowlist.json b/scripts/plugin-boundary-allowlist.json index 619466288..8d5195d0c 100644 --- a/scripts/plugin-boundary-allowlist.json +++ b/scripts/plugin-boundary-allowlist.json @@ -111,16 +111,6 @@ "plugins/proxmox/src/frontend/HostProxmoxTab.tsx", "plugins/proxmox/src/frontend/ProxmoxDiscoverDialog.tsx", "plugins/proxmox/src/frontend/index.tsx", - "plugins/remote-desktop/src/frontend/GuacamoleApp.tsx", - "plugins/remote-desktop/src/frontend/GuacamoleDisplay.tsx", - "plugins/remote-desktop/src/frontend/GuacamoleFileBrowser.tsx", - "plugins/remote-desktop/src/frontend/GuacamoleToolbar.tsx", - "plugins/remote-desktop/src/frontend/HostEditorGuacamoleTabs.tsx", - "plugins/remote-desktop/src/frontend/guacamole-api.ts", - "plugins/remote-desktop/src/frontend/guacamole-session-id.ts", - "plugins/remote-desktop/src/frontend/index.tsx", - "plugins/remote-desktop/src/frontend/quick-connect-guac-host.ts", - "plugins/remote-desktop/src/frontend/rdp-credential-prompt.ts", "plugins/tailscale/src/frontend/TailscaleCheckDialog.tsx", "plugins/tailscale/src/frontend/TailscaleDevicesPanel.tsx", "plugins/tailscale/src/frontend/TailscaleDevicesStatus.tsx", @@ -194,9 +184,6 @@ "plugins/host-metrics/src/backend/widgets/temperature-collector.ts", "plugins/host-metrics/src/backend/widgets/uptime-collector.ts", "plugins/proxmox/src/backend/routes.ts", - "plugins/remote-desktop/src/backend/guacamole-server.ts", - "plugins/remote-desktop/src/backend/routes.ts", - "plugins/remote-desktop/src/backend/token-service.ts", "plugins/tailscale/src/backend/host-metrics-manager.ts", "plugins/tailscale/src/backend/routes.ts" ], diff --git a/scripts/shell-plugin-id-allowlist.json b/scripts/shell-plugin-id-allowlist.json index 162ff2d02..35c9e6613 100644 --- a/scripts/shell-plugin-id-allowlist.json +++ b/scripts/shell-plugin-id-allowlist.json @@ -1,29 +1,17 @@ { "$comment": "Literals src/ui may still spell that name a plugin id or a plugin-owned view. Each is data, not knowledge of a plugin. Checked by scripts/check-shell-plugin-ids.cjs; D1 and Phase B empty it.", "files": { - "src/ui/lib/connection-origin.ts": { - "literals": ["rdp", "telnet", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableTelnet, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." - }, "src/ui/sidebar/HostEditorData.ts": { - "literals": ["docker", "rdp", "telnet", "vnc"], - "reason": "Host data: the enableDocker column filter and the container runtime name, until Phase B moves them. Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableTelnet, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." - }, - "src/ui/sidebar/HostManagerData.ts": { - "literals": ["rdp", "telnet", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableTelnet, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." + "literals": ["docker"], + "reason": "Host data: the enableDocker column filter and the container runtime name, until Phase B moves them." }, "src/ui/sidebar/HostsPanel.tsx": { - "literals": ["docker", "rdp", "telnet", "vnc"], - "reason": "Host data: the enableDocker column filter and the container runtime name, until Phase B moves them. Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableTelnet, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." + "literals": ["docker"], + "reason": "Host data: the enableDocker column filter and the container runtime name, until Phase B moves them." }, "src/ui/sidebar/QuickConnectPanel.tsx": { - "literals": ["files", "rdp", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it. The quick-connect-for-an-unsaved-host flow offers a fixed Files button next to the default connect button, not an extensible list a plugin contributes to." - }, - "src/ui/sidebar/quick-connect-host.ts": { - "literals": ["rdp", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." + "literals": ["files"], + "reason": "The quick-connect-for-an-unsaved-host flow offers a fixed Files button next to the default connect button, not an extensible list a plugin contributes to." } } } diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index 1dfeb9fce..c4b99ffbf 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -819,9 +819,6 @@ app.post("/database/export", authenticateJWT, async (req, res) => { socks5_password TEXT, socks5_proxy_chain TEXT, domain TEXT, - security TEXT, - ignore_cert INTEGER NOT NULL DEFAULT 0, - guacamole_config TEXT, mac_address TEXT, port_knock_sequence TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -931,8 +928,8 @@ app.post("/database/export", authenticateJWT, async (req, res) => { const sshHosts = await createCurrentHostRepository().listDecryptedByUserId(userId); const insertHost = exportDb.prepare(` - INSERT INTO ssh_data (id, user_id, connection_type, name, ip, port, username, folder, tags, pin, auth_type, force_keyboard_interactive, password, key, key_password, key_type, sudo_password, autostart_password, autostart_key, autostart_key_password, credential_id, override_credential_username, enable_terminal, enable_tunnel, tunnel_connections, jump_hosts, enable_file_manager, enable_docker, enable_web_ui, show_terminal_in_sidebar, show_file_manager_in_sidebar, show_tunnel_in_sidebar, show_docker_in_sidebar, show_server_stats_in_sidebar, default_path, stats_config, docker_config, web_ui_config, terminal_config, quick_actions, notes, use_socks5, socks5_host, socks5_port, socks5_username, socks5_password, socks5_proxy_chain, domain, security, ignore_cert, guacamole_config, mac_address, port_knock_sequence, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO ssh_data (id, user_id, connection_type, name, ip, port, username, folder, tags, pin, auth_type, force_keyboard_interactive, password, key, key_password, key_type, sudo_password, autostart_password, autostart_key, autostart_key_password, credential_id, override_credential_username, enable_terminal, enable_tunnel, tunnel_connections, jump_hosts, enable_file_manager, enable_docker, enable_web_ui, show_terminal_in_sidebar, show_file_manager_in_sidebar, show_tunnel_in_sidebar, show_docker_in_sidebar, show_server_stats_in_sidebar, default_path, stats_config, docker_config, web_ui_config, terminal_config, quick_actions, notes, use_socks5, socks5_host, socks5_port, socks5_username, socks5_password, socks5_proxy_chain, domain, mac_address, port_knock_sequence, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); for (const decrypted of sshHosts) { @@ -985,9 +982,6 @@ app.post("/database/export", authenticateJWT, async (req, res) => { decrypted.socks5Password || null, decrypted.socks5ProxyChain || null, decrypted.domain || null, - decrypted.security || null, - decrypted.ignoreCert ? 1 : 0, - decrypted.guacamoleConfig || null, decrypted.macAddress || null, decrypted.portKnockSequence || null, decrypted.createdAt, diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index f3d9b520c..63dff7cc0 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -19,7 +19,6 @@ import { assertDataDirIsNotMisconfigured, DataDirMisconfiguredError, } from "../../utils/data-dir-guard.js"; -import { getDefaultGuacdUrl } from "../../utils/guacd-config.js"; import { resolveDatabaseDialect, type DatabaseDialect } from "./dialect.js"; import { SYSTEM_ROLE_DEFAULTS } from "../../utils/permission-catalog.js"; import { connectRemoteDatabase } from "./connect.js"; @@ -807,24 +806,6 @@ async function initializeCompleteDatabase(): Promise { error: e, }); } - - try { - ensureRawSettingDefault("guac_enabled", "true"); - } catch (e) { - databaseLogger.warn("Could not initialize guac_enabled setting", { - operation: "db_init", - error: e, - }); - } - - try { - ensureRawSettingDefault("guac_url", getDefaultGuacdUrl()); - } catch (e) { - databaseLogger.warn("Could not initialize guac_url setting", { - operation: "db_init", - error: e, - }); - } } const addColumnIfNotExists = ( @@ -948,7 +929,6 @@ const migrateSchema = () => { addColumnIfNotExists("user_preferences", "custom_themes", "TEXT"); addColumnIfNotExists("user_preferences", "custom_keybindings", "TEXT"); addColumnIfNotExists("user_preferences", "terminal_defaults", "TEXT"); - addColumnIfNotExists("user_preferences", "rdp_defaults", "TEXT"); addColumnIfNotExists("user_preferences", "terminal_macros", "TEXT"); sqlite.exec(` @@ -1164,9 +1144,6 @@ const migrateSchema = () => { addColumnIfNotExists("ssh_data", "connection_type", 'TEXT NOT NULL DEFAULT "ssh"'); addColumnIfNotExists("ssh_data", "domain", "TEXT"); - addColumnIfNotExists("ssh_data", "security", "TEXT"); - addColumnIfNotExists("ssh_data", "ignore_cert", "INTEGER NOT NULL DEFAULT 0"); - addColumnIfNotExists("ssh_data", "guacamole_config", "TEXT"); addColumnIfNotExists("ssh_data", "notes", "TEXT"); addColumnIfNotExists("ssh_data", "use_socks5", "INTEGER"); @@ -1554,9 +1531,6 @@ const migrateSchema = () => { { column: "show_server_stats_in_sidebar", sql: "ALTER TABLE ssh_data ADD COLUMN show_server_stats_in_sidebar INTEGER NOT NULL DEFAULT 0" }, { column: "quick_actions", sql: "ALTER TABLE ssh_data ADD COLUMN quick_actions TEXT" }, { column: "domain", sql: "ALTER TABLE ssh_data ADD COLUMN domain TEXT" }, - { column: "security", sql: "ALTER TABLE ssh_data ADD COLUMN security TEXT" }, - { column: "ignore_cert", sql: "ALTER TABLE ssh_data ADD COLUMN ignore_cert INTEGER NOT NULL DEFAULT 0" }, - { column: "guacamole_config", sql: "ALTER TABLE ssh_data ADD COLUMN guacamole_config TEXT" }, { column: "socks5_proxy_chain", sql: "ALTER TABLE ssh_data ADD COLUMN socks5_proxy_chain TEXT" }, { column: "host_key_fingerprint", sql: "ALTER TABLE ssh_data ADD COLUMN host_key_fingerprint TEXT" }, { column: "host_key_type", sql: "ALTER TABLE ssh_data ADD COLUMN host_key_type TEXT" }, @@ -1567,18 +1541,10 @@ const migrateSchema = () => { { column: "mac_address", sql: "ALTER TABLE ssh_data ADD COLUMN mac_address TEXT" }, { column: "port_knock_sequence", sql: "ALTER TABLE ssh_data ADD COLUMN port_knock_sequence TEXT" }, { column: "enable_ssh", sql: "ALTER TABLE ssh_data ADD COLUMN enable_ssh INTEGER NOT NULL DEFAULT 1" }, - { column: "enable_rdp", sql: "ALTER TABLE ssh_data ADD COLUMN enable_rdp INTEGER NOT NULL DEFAULT 0" }, - { column: "enable_vnc", sql: "ALTER TABLE ssh_data ADD COLUMN enable_vnc INTEGER NOT NULL DEFAULT 0" }, - { column: "enable_telnet", sql: "ALTER TABLE ssh_data ADD COLUMN enable_telnet INTEGER NOT NULL DEFAULT 0" }, { column: "ssh_port", sql: "ALTER TABLE ssh_data ADD COLUMN ssh_port INTEGER DEFAULT 22" }, - { column: "rdp_port", sql: "ALTER TABLE ssh_data ADD COLUMN rdp_port INTEGER DEFAULT 3389" }, - { column: "vnc_port", sql: "ALTER TABLE ssh_data ADD COLUMN vnc_port INTEGER DEFAULT 5900" }, - { column: "telnet_port", sql: "ALTER TABLE ssh_data ADD COLUMN telnet_port INTEGER DEFAULT 23" }, { column: "rdp_user", sql: "ALTER TABLE ssh_data ADD COLUMN rdp_user TEXT" }, { column: "rdp_password", sql: "ALTER TABLE ssh_data ADD COLUMN rdp_password TEXT" }, { column: "rdp_domain", sql: "ALTER TABLE ssh_data ADD COLUMN rdp_domain TEXT" }, - { column: "rdp_security", sql: "ALTER TABLE ssh_data ADD COLUMN rdp_security TEXT" }, - { column: "rdp_ignore_cert", sql: "ALTER TABLE ssh_data ADD COLUMN rdp_ignore_cert INTEGER DEFAULT 0" }, { column: "vnc_password", sql: "ALTER TABLE ssh_data ADD COLUMN vnc_password TEXT" }, { column: "vnc_user", sql: "ALTER TABLE ssh_data ADD COLUMN vnc_user TEXT" }, { column: "telnet_user", sql: "ALTER TABLE ssh_data ADD COLUMN telnet_user TEXT" }, diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index 511c23b79..6c8e15390 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -302,21 +302,13 @@ export const hosts = mysqlTable( quickActions: text("quick_actions"), notes: text("notes"), enableSsh: boolean("enable_ssh").notNull().default(true), - enableRdp: boolean("enable_rdp").notNull().default(false), - enableVnc: boolean("enable_vnc").notNull().default(false), - enableTelnet: boolean("enable_telnet").notNull().default(false), sshPort: int("ssh_port").default(22), - rdpPort: int("rdp_port").default(3389), - vncPort: int("vnc_port").default(5900), - telnetPort: int("telnet_port").default(23), rdpCredentialId: int("rdp_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), rdpUser: text("rdp_user"), rdpPassword: text("rdp_password"), rdpDomain: text("rdp_domain"), - rdpSecurity: text("rdp_security"), - rdpIgnoreCert: boolean("rdp_ignore_cert").default(false), vncCredentialId: int("vnc_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), vncPassword: text("vnc_password"), @@ -331,9 +323,6 @@ export const hosts = mysqlTable( telnetAuthType: text("telnet_auth_type"), domain: text("domain"), - security: text("security"), - ignoreCert: boolean("ignore_cert").default(false), - guacamoleConfig: text("guacamole_config"), useSocks5: boolean("use_socks5"), socks5Host: text("socks5_host"), @@ -892,7 +881,6 @@ export const userPreferences = mysqlTable("user_preferences", { customThemes: text("custom_themes"), customKeybindings: text("custom_keybindings"), terminalDefaults: text("terminal_defaults"), - rdpDefaults: text("rdp_defaults"), terminalMacros: text("terminal_macros"), updatedAt: varchar("updated_at", { length: 255 }) .notNull() diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index 839ffa38f..b31a66c3d 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -303,21 +303,13 @@ export const hosts = pgTable( quickActions: text("quick_actions"), notes: text("notes"), enableSsh: boolean("enable_ssh").notNull().default(true), - enableRdp: boolean("enable_rdp").notNull().default(false), - enableVnc: boolean("enable_vnc").notNull().default(false), - enableTelnet: boolean("enable_telnet").notNull().default(false), sshPort: integer("ssh_port").default(22), - rdpPort: integer("rdp_port").default(3389), - vncPort: integer("vnc_port").default(5900), - telnetPort: integer("telnet_port").default(23), rdpCredentialId: integer("rdp_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), rdpUser: text("rdp_user"), rdpPassword: text("rdp_password"), rdpDomain: text("rdp_domain"), - rdpSecurity: text("rdp_security"), - rdpIgnoreCert: boolean("rdp_ignore_cert").default(false), vncCredentialId: integer("vnc_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), vncPassword: text("vnc_password"), @@ -332,9 +324,6 @@ export const hosts = pgTable( telnetAuthType: text("telnet_auth_type"), domain: text("domain"), - security: text("security"), - ignoreCert: boolean("ignore_cert").default(false), - guacamoleConfig: text("guacamole_config"), useSocks5: boolean("use_socks5"), socks5Host: text("socks5_host"), @@ -893,7 +882,6 @@ export const userPreferences = pgTable("user_preferences", { customThemes: text("custom_themes"), customKeybindings: text("custom_keybindings"), terminalDefaults: text("terminal_defaults"), - rdpDefaults: text("rdp_defaults"), terminalMacros: text("terminal_macros"), updatedAt: varchar("updated_at", { length: 255 }) .notNull() diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index 75bf28aab..8daf477ba 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -295,21 +295,13 @@ export const hosts = sqliteTable( quickActions: text("quick_actions"), notes: text("notes"), enableSsh: integer("enable_ssh", { mode: "boolean" }).notNull().default(true), - enableRdp: integer("enable_rdp", { mode: "boolean" }).notNull().default(false), - enableVnc: integer("enable_vnc", { mode: "boolean" }).notNull().default(false), - enableTelnet: integer("enable_telnet", { mode: "boolean" }).notNull().default(false), sshPort: integer("ssh_port").default(22), - rdpPort: integer("rdp_port").default(3389), - vncPort: integer("vnc_port").default(5900), - telnetPort: integer("telnet_port").default(23), rdpCredentialId: integer("rdp_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), rdpUser: text("rdp_user"), rdpPassword: text("rdp_password"), rdpDomain: text("rdp_domain"), - rdpSecurity: text("rdp_security"), - rdpIgnoreCert: integer("rdp_ignore_cert", { mode: "boolean" }).default(false), vncCredentialId: integer("vnc_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), vncPassword: text("vnc_password"), @@ -324,9 +316,6 @@ export const hosts = sqliteTable( telnetAuthType: text("telnet_auth_type"), domain: text("domain"), - security: text("security"), - ignoreCert: integer("ignore_cert", { mode: "boolean" }).default(false), - guacamoleConfig: text("guacamole_config"), useSocks5: integer("use_socks5", { mode: "boolean" }), socks5Host: text("socks5_host"), @@ -889,7 +878,6 @@ export const userPreferences = sqliteTable("user_preferences", { customThemes: text("custom_themes"), customKeybindings: text("custom_keybindings"), terminalDefaults: text("terminal_defaults"), - rdpDefaults: text("rdp_defaults"), terminalMacros: text("terminal_macros"), updatedAt: text("updated_at") .notNull() diff --git a/src/backend/database/routes/host-bulk-routes.ts b/src/backend/database/routes/host-bulk-routes.ts index c9fe0af8f..2c5ac5f05 100644 --- a/src/backend/database/routes/host-bulk-routes.ts +++ b/src/backend/database/routes/host-bulk-routes.ts @@ -812,9 +812,6 @@ export function registerHostBulkRoutes( ? 1 : 0, enableSsh: hostData.enableSsh ?? effectiveConnectionType === "ssh", - enableRdp: hostData.enableRdp ?? false, - enableVnc: hostData.enableVnc ?? false, - enableTelnet: hostData.enableTelnet ?? false, updatedAt: new Date().toISOString(), }; @@ -828,21 +825,10 @@ export function registerHostBulkRoutes( sshDataObj.rdpUser = hostData.rdpUser || null; sshDataObj.rdpPassword = hostData.rdpPassword || null; sshDataObj.rdpDomain = hostData.rdpDomain || null; - sshDataObj.rdpSecurity = hostData.rdpSecurity || null; - sshDataObj.rdpIgnoreCert = hostData.rdpIgnoreCert ? 1 : 0; - sshDataObj.rdpPort = hostData.rdpPort || 3389; sshDataObj.vncUser = hostData.vncUser || null; sshDataObj.vncPassword = hostData.vncPassword || null; - sshDataObj.vncPort = hostData.vncPort || 5900; sshDataObj.telnetUser = hostData.telnetUser || null; sshDataObj.telnetPassword = hostData.telnetPassword || null; - sshDataObj.telnetPort = hostData.telnetPort || 23; - sshDataObj.enableRdp = hostData.enableRdp ? 1 : 0; - sshDataObj.enableVnc = hostData.enableVnc ? 1 : 0; - sshDataObj.enableTelnet = hostData.enableTelnet ? 1 : 0; - sshDataObj.guacamoleConfig = hostData.guacamoleConfig - ? JSON.stringify(hostData.guacamoleConfig) - : null; } else { sshDataObj.password = hostData.authType === "password" ? hostData.password : null; @@ -855,9 +841,6 @@ export function registerHostBulkRoutes( sshDataObj.keyType = hostData.authType === "key" ? hostData.keyType || "auto" : null; sshDataObj.domain = null; - sshDataObj.security = null; - sshDataObj.ignoreCert = 0; - sshDataObj.guacamoleConfig = null; } const lookupKey = `${hostData.ip}:${hostData.port}:${hostData.username}`; @@ -1079,9 +1062,6 @@ export function registerHostBulkRoutes( portKnockSequence: null, overrideCredentialUsername: 0, enableSsh: true, - enableRdp: false, - enableVnc: false, - enableTelnet: false, updatedAt: new Date().toISOString(), }; diff --git a/src/backend/database/routes/host-normalizers.ts b/src/backend/database/routes/host-normalizers.ts index 83d8d34cc..cc6eddb43 100644 --- a/src/backend/database/routes/host-normalizers.ts +++ b/src/backend/database/routes/host-normalizers.ts @@ -23,12 +23,7 @@ export function applyHostKeyTypeUpdate( } } -const PROTOCOL_ENABLE_FIELDS = [ - "enableSsh", - "enableRdp", - "enableVnc", - "enableTelnet", -] as const; +const PROTOCOL_ENABLE_FIELDS = ["enableSsh"] as const; export function normalizeProtocolEnableFields( values: Record, @@ -214,13 +209,7 @@ export type NormalizedImportedHost = Record & { portKnockSequence?: unknown; overrideCredentialUsername?: unknown; domain?: unknown; - security?: unknown; - ignoreCert?: unknown; - guacamoleConfig?: unknown; enableSsh: boolean; - enableRdp: boolean; - enableVnc: boolean; - enableTelnet: boolean; }; export function normalizeImportedHost( @@ -275,18 +264,6 @@ export function normalizeImportedHost( hostData.enableSsh === undefined ? connectionType === "ssh" : asBoolean(hostData.enableSsh), - enableRdp: - hostData.enableRdp === undefined - ? connectionType === "rdp" - : asBoolean(hostData.enableRdp), - enableVnc: - hostData.enableVnc === undefined - ? connectionType === "vnc" - : asBoolean(hostData.enableVnc), - enableTelnet: - hostData.enableTelnet === undefined - ? connectionType === "telnet" - : asBoolean(hostData.enableTelnet), }; } @@ -372,14 +349,8 @@ const CONNECT_LEVEL_FIELDS = new Set([ "showDockerInSidebar", "showServerStatsInSidebar", "enableSsh", - "enableRdp", - "enableVnc", - "enableTelnet", "sshPort", - "rdpPort", "rdpAuthType", - "vncPort", - "telnetPort", "defaultPath", "scpLegacy", "tunnelConnections", @@ -472,31 +443,10 @@ export function transformHostResponse( showTunnelInSidebar: !!host.showTunnelInSidebar, showDockerInSidebar: !!host.showDockerInSidebar, showServerStatsInSidebar: !!host.showServerStatsInSidebar, - // Old hosts only had connection_type set; the per-protocol enable flags didn't exist yet. - // The schema defaults (enableSsh=true, others=false) wrongly mark every old host as SSH. - // Detect this migration case: if no non-SSH protocol is explicitly enabled AND - // connectionType is set to a non-SSH value, fall back to inferring from connectionType. - ...(() => { - const ct = host.connectionType; - const rdp = !!host.enableRdp; - const vnc = !!host.enableVnc; - const tel = !!host.enableTelnet; - const isMigratedNonSsh = !rdp && !vnc && !tel && ct && ct !== "ssh"; - return { - enableSsh: isMigratedNonSsh ? false : !!host.enableSsh, - enableRdp: isMigratedNonSsh ? ct === "rdp" : rdp, - enableVnc: isMigratedNonSsh ? ct === "vnc" : vnc, - enableTelnet: isMigratedNonSsh ? ct === "telnet" : tel, - }; - })(), + enableSsh: !!host.enableSsh, sshPort: host.sshPort ?? host.port ?? 22, - rdpPort: host.rdpPort ?? 3389, - vncPort: host.vncPort ?? 5900, - telnetPort: host.telnetPort ?? 23, rdpUser: host.rdpUser || undefined, rdpDomain: host.rdpDomain || undefined, - rdpSecurity: host.rdpSecurity || undefined, - rdpIgnoreCert: !!host.rdpIgnoreCert, vncUser: host.vncUser || undefined, telnetUser: host.telnetUser || undefined, tunnelConnections: host.tunnelConnections @@ -524,10 +474,5 @@ export function transformHostResponse( ? JSON.parse(host.portKnockSequence as string) : [], domain: host.domain || undefined, - security: host.security || undefined, - ignoreCert: !!host.ignoreCert, - guacamoleConfig: host.guacamoleConfig - ? JSON.parse(host.guacamoleConfig as string) - : undefined, }; } diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index 917f30431..1edad51d3 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -68,7 +68,6 @@ import type { HostResolutionCredentialRecord, HostResolutionHostRecord, } from "../repositories/host-resolution-repository.js"; -import { AUTH_PROTOCOL_METADATA } from "../../../types/auth-protocols.js"; import { requiresPersonalHostAuthentication, resolveRecipientSharedHostAuthentication, @@ -216,9 +215,6 @@ router.post( terminalConfig, forceKeyboardInteractive, domain, - security, - ignoreCert, - guacamoleConfig, notes, useSocks5, socks5Host, @@ -232,20 +228,12 @@ router.post( macAddress, wolBroadcastAddress, enableSsh, - enableRdp, - enableVnc, - enableTelnet, sshPort, - rdpPort, - vncPort, - telnetPort, rdpAuthType, rdpCredentialId, rdpUser, rdpPassword, rdpDomain, - rdpSecurity, - rdpIgnoreCert, vncAuthType, vncCredentialId, vncPassword, @@ -267,7 +255,7 @@ router.post( !isNonEmptyString(ip) || !isValidPort(port) || !isOptionalBoolean(shareSshAuth) || - ![enableSsh, enableRdp, enableVnc, enableTelnet].every(isOptionalBoolean) + !isOptionalBoolean(enableSsh) ) { sshLogger.warn("Invalid SSH data input validation failed", { operation: "host_create", @@ -364,9 +352,6 @@ router.post( : null, forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false", domain: domain || null, - security: security || null, - ignoreCert: ignoreCert ? 1 : 0, - guacamoleConfig: guacamoleConfig ? JSON.stringify(guacamoleConfig) : null, notes: notes || null, sudoPassword: sudoPassword || null, useSocks5: useSocks5 ? 1 : 0, @@ -388,27 +373,22 @@ router.post( : null, ...normalizeProtocolEnableFields(hostData), sshPort: sshPort || port || 22, - rdpPort: rdpPort || 3389, - vncPort: vncPort || 5900, - telnetPort: telnetPort || 23, - rdpAuthType: enableRdp ? rdpAuthType || null : null, + rdpAuthType: rdpAuthType || null, rdpCredentialId: - enableRdp && rdpAuthType === "credential" && rdpCredentialId + rdpAuthType === "credential" && rdpCredentialId ? rdpCredentialId : null, rdpUser: rdpUser || null, rdpDomain: rdpDomain || null, - rdpSecurity: rdpSecurity || null, - rdpIgnoreCert: rdpIgnoreCert ? 1 : 0, - vncAuthType: enableVnc ? vncAuthType || null : null, + vncAuthType: vncAuthType || null, vncCredentialId: - enableVnc && vncAuthType === "credential" && vncCredentialId + vncAuthType === "credential" && vncCredentialId ? vncCredentialId : null, vncUser: vncUser || null, - telnetAuthType: enableTelnet ? telnetAuthType || null : null, + telnetAuthType: telnetAuthType || null, telnetCredentialId: - enableTelnet && telnetAuthType === "credential" && telnetCredentialId + telnetAuthType === "credential" && telnetCredentialId ? telnetCredentialId : null, telnetUser: telnetUser || null, @@ -932,9 +912,6 @@ router.put( terminalConfig, forceKeyboardInteractive, domain, - security, - ignoreCert, - guacamoleConfig, notes, useSocks5, socks5Host, @@ -948,20 +925,12 @@ router.put( macAddress, wolBroadcastAddress, enableSsh, - enableRdp, - enableVnc, - enableTelnet, sshPort, - rdpPort, - vncPort, - telnetPort, rdpAuthType, rdpCredentialId, rdpUser, rdpPassword, rdpDomain, - rdpSecurity, - rdpIgnoreCert, vncAuthType, vncCredentialId, vncPassword, @@ -983,9 +952,7 @@ router.put( !isNonEmptyString(ip) || !isValidPort(port) || !isOptionalBoolean(shareSshAuth) || - ![enableSsh, enableRdp, enableVnc, enableTelnet].every( - isOptionalBoolean, - ) || + !isOptionalBoolean(enableSsh) || !hostId ) { sshLogger.warn("Invalid SSH data input validation failed for update", { @@ -1083,9 +1050,6 @@ router.put( : null, forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false", domain: domain || null, - security: security || null, - ignoreCert: ignoreCert ? 1 : 0, - guacamoleConfig: guacamoleConfig ? JSON.stringify(guacamoleConfig) : null, notes: notes || null, sudoPassword: sudoPassword || null, useSocks5: useSocks5 ? 1 : 0, @@ -1107,27 +1071,22 @@ router.put( : null, ...normalizeProtocolEnableFields(hostData), sshPort: sshPort || port || 22, - rdpPort: rdpPort || 3389, - vncPort: vncPort || 5900, - telnetPort: telnetPort || 23, - rdpAuthType: enableRdp ? rdpAuthType || null : null, + rdpAuthType: rdpAuthType || null, rdpCredentialId: - enableRdp && rdpAuthType === "credential" && rdpCredentialId + rdpAuthType === "credential" && rdpCredentialId ? rdpCredentialId : null, rdpUser: rdpUser || null, rdpDomain: rdpDomain || null, - rdpSecurity: rdpSecurity || null, - rdpIgnoreCert: rdpIgnoreCert ? 1 : 0, - vncAuthType: enableVnc ? vncAuthType || null : null, + vncAuthType: vncAuthType || null, vncCredentialId: - enableVnc && vncAuthType === "credential" && vncCredentialId + vncAuthType === "credential" && vncCredentialId ? vncCredentialId : null, vncUser: vncUser || null, - telnetAuthType: enableTelnet ? telnetAuthType || null : null, + telnetAuthType: telnetAuthType || null, telnetCredentialId: - enableTelnet && telnetAuthType === "credential" && telnetCredentialId + telnetAuthType === "credential" && telnetCredentialId ? telnetCredentialId : null, telnetUser: telnetUser || null, @@ -2085,9 +2044,7 @@ router.get( const exportedConnectionType = (resolvedHost.connectionType as string) || "ssh"; - const isRemoteDesktop = ["rdp", "vnc", "telnet"].includes( - exportedConnectionType, - ); + const isRemoteDesktop = exportedConnectionType !== "ssh"; const baseExportData = { exportId: resolvedHost.id, @@ -2104,24 +2061,18 @@ router.get( : resolvedHost.tags || [], pin: !!resolvedHost.pin, notes: resolvedHost.notes || null, + // Every plugin's host settings, secrets redacted, for import to hand back. + pluginSettings: hostPluginSettings ?? {}, }; const exportData = isRemoteDesktop ? { ...baseExportData, - enableRdp: !!resolvedHost.enableRdp, - enableVnc: !!resolvedHost.enableVnc, - enableTelnet: !!resolvedHost.enableTelnet, - rdpPort: resolvedHost.rdpPort || 3389, - vncPort: resolvedHost.vncPort || 5900, - telnetPort: resolvedHost.telnetPort || 23, rdpAuthType: resolvedHost.rdpAuthType || null, rdpCredentialId: resolvedHost.rdpCredentialId || null, rdpUser: resolvedHost.rdpUser || null, rdpPassword: resolvedHost.rdpPassword || null, rdpDomain: resolvedHost.rdpDomain || null, - rdpSecurity: resolvedHost.rdpSecurity || null, - rdpIgnoreCert: !!resolvedHost.rdpIgnoreCert, vncAuthType: resolvedHost.vncAuthType || null, vncCredentialId: resolvedHost.vncCredentialId || null, vncUser: resolvedHost.vncUser || null, @@ -2130,9 +2081,6 @@ router.get( telnetCredentialId: resolvedHost.telnetCredentialId || null, telnetUser: resolvedHost.telnetUser || null, telnetPassword: resolvedHost.telnetPassword || null, - guacamoleConfig: resolvedHost.guacamoleConfig - ? JSON.parse(resolvedHost.guacamoleConfig as string) - : null, } : { ...baseExportData, @@ -2273,9 +2221,7 @@ router.get( const exportedConnectionType = (resolvedHost.connectionType as string) || "ssh"; - const isRemoteDesktop = ["rdp", "vnc", "telnet"].includes( - exportedConnectionType, - ); + const isRemoteDesktop = exportedConnectionType !== "ssh"; const baseExportData = { exportId: resolvedHost.id, @@ -2292,17 +2238,14 @@ router.get( : resolvedHost.tags || [], pin: !!resolvedHost.pin, notes: resolvedHost.notes || null, + // Every plugin's host settings, secrets redacted, for import to hand back. + pluginSettings: hostPluginSettings ?? {}, }; const exportData = isRemoteDesktop ? { ...baseExportData, domain: resolvedHost.domain || null, - security: resolvedHost.security || null, - ignoreCert: !!resolvedHost.ignoreCert, - guacamoleConfig: resolvedHost.guacamoleConfig - ? JSON.parse(resolvedHost.guacamoleConfig as string) - : null, } : { ...baseExportData, @@ -2638,8 +2581,9 @@ async function resolveHostCredentials( host.id, requestingUserId, ); + // Whether a protocol is on is the remote-desktop plugin's host + // setting; the client only offers an override for one that is. for (const protocol of ["rdp", "vnc", "telnet"] as const) { - if (!host[AUTH_PROTOCOL_METADATA[protocol].enableField]) continue; authOverrides[protocol] = { credentialId: overrideCredentialIds[protocol], required: false, diff --git a/src/backend/database/routes/user-preferences.ts b/src/backend/database/routes/user-preferences.ts index 40a032f93..a19750582 100644 --- a/src/backend/database/routes/user-preferences.ts +++ b/src/backend/database/routes/user-preferences.ts @@ -39,11 +39,10 @@ const pickPreferences = (row?: UserPreferenceRecord | null) => ({ customThemes: row?.customThemes ?? null, customKeybindings: row?.customKeybindings ?? null, terminalDefaults: row?.terminalDefaults ?? null, - rdpDefaults: row?.rdpDefaults ?? null, terminalMacros: row?.terminalMacros ?? null, }); -const connectionDefaultFields = ["terminalDefaults", "rdpDefaults"] as const; +const connectionDefaultFields = ["terminalDefaults"] as const; export function validateDefaultsJson(value: string): boolean { if (value.length > 32_768) return false; @@ -233,7 +232,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => { customThemes, customKeybindings, terminalDefaults, - rdpDefaults, terminalMacros, } = req.body as { reopenTabsOnLogin?: boolean; @@ -256,7 +254,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => { customThemes?: string | null; customKeybindings?: string | null; terminalDefaults?: string | null; - rdpDefaults?: string | null; terminalMacros?: string | null; }; // showHostTags, hostTrayOnClick, compactHostView, statusColorScheme are no @@ -289,7 +286,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => { customThemes, customKeybindings, terminalDefaults, - rdpDefaults, terminalMacros, })) { if (value !== undefined && value !== null && typeof value !== "string") { @@ -299,7 +295,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => { const connectionDefaults = { terminalDefaults, - rdpDefaults, }; for (const key of connectionDefaultFields) { const value = connectionDefaults[key]; @@ -435,7 +430,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => { updates.customKeybindings = customKeybindings; if (terminalDefaults !== undefined) updates.terminalDefaults = terminalDefaults; - if (rdpDefaults !== undefined) updates.rdpDefaults = rdpDefaults; if (terminalMacros !== undefined) updates.terminalMacros = terminalMacros; if (Object.keys(updates).length === 1) { diff --git a/src/backend/database/routes/user-settings-routes.ts b/src/backend/database/routes/user-settings-routes.ts index 89c1c6bfa..a7b7a3ece 100644 --- a/src/backend/database/routes/user-settings-routes.ts +++ b/src/backend/database/routes/user-settings-routes.ts @@ -1,12 +1,6 @@ import type { AuthenticatedRequest } from "../../../types/index.js"; import { getErrorMessage } from "../../utils/error-message.js"; import type { RequestHandler, Router } from "express"; -// Remote Desktop is a first-party plugin (plugins/remote-desktop); this goes -// through the same in-core bridge its router is mounted behind, rather than -// importing the plugin's TypeScript directly, since plugins/ is compiled -// separately from src/backend/ and core cannot statically import across that -// boundary. See guacamole-dispatch.ts and that plugin's README. -import { restartGuacamoleService as restartGuacServer } from "../../hosts/guacamole-sessions.js"; import { authLogger, getGlobalLogLevel, @@ -31,10 +25,6 @@ import { } from "../repositories/factory.js"; import type { UserRecord } from "../repositories/user-repository.js"; -function getDefaultGuacUrl(): string { - return `${process.env.GUACD_HOST || "localhost"}:${process.env.GUACD_PORT || "4822"}`; -} - export type HostDefaults = { useSocks5?: boolean; socks5Host?: string; @@ -66,119 +56,6 @@ export function registerUserSettingsRoutes( router: Router, authenticateJWT: RequestHandler, ): void { - /** - * @openapi - * /users/guacamole-settings: - * get: - * summary: Get Guacamole settings - * description: Returns current guacd enabled status and host:port URL. No authentication required. - * tags: - * - Users - * responses: - * 200: - * description: Guacamole settings. - * content: - * application/json: - * schema: - * type: object - * properties: - * enabled: - * type: boolean - * url: - * type: string - * 500: - * description: Failed to get guacamole settings. - */ - router.get("/guacamole-settings", authenticateJWT, async (_req, res) => { - try { - const settings = createCurrentSettingsRepository(); - const enabled = await settings.getBoolean("guac_enabled", true); - const url = await settings.get("guac_url"); - res.json({ - enabled, - url: url ?? getDefaultGuacUrl(), - }); - } catch (err) { - authLogger.error("Failed to get guacamole settings", err); - res.status(500).json({ error: "Failed to get guacamole settings" }); - } - }); - - /** - * @openapi - * /users/guacamole-settings: - * patch: - * summary: Update Guacamole settings - * description: Admin-only. Updates guacd enabled status and/or host:port URL. - * tags: - * - Users - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * enabled: - * type: boolean - * url: - * type: string - * responses: - * 200: - * description: Guacamole settings updated. - * 403: - * description: Not authorized. - * 500: - * description: Failed to update guacamole settings. - */ - router.patch("/guacamole-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 { enabled, url } = req.body; - const settings = createCurrentSettingsRepository(); - if (typeof enabled === "boolean") { - await settings.set("guac_enabled", enabled ? "true" : "false"); - } - if (typeof url === "string") { - await settings.set("guac_url", url); - try { - await restartGuacServer(); - } catch (err) { - authLogger.error( - "Failed to restart guac server after URL update", - err, - ); - } - } - const currentEnabled = await settings.getBoolean("guac_enabled", true); - const currentUrl = await settings.get("guac_url"); - - const { ipAddress, userAgent } = getRequestMeta(req); - await logAudit({ - userId, - username: actor.username ?? userId, - action: "update_guacamole_settings", - resourceType: "setting", - details: JSON.stringify({ enabled, url }), - ipAddress, - userAgent, - success: true, - }); - - res.json({ - enabled: currentEnabled, - url: currentUrl ?? getDefaultGuacUrl(), - }); - } catch (err) { - authLogger.error("Failed to update guacamole settings", err); - res.status(500).json({ error: "Failed to update guacamole settings" }); - } - }); - /** * @openapi * /users/log-level: diff --git a/src/backend/hosts/guacamole-sessions.ts b/src/backend/hosts/guacamole-sessions.ts deleted file mode 100644 index e6ac4b22b..000000000 --- a/src/backend/hosts/guacamole-sessions.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * What core needs from the Remote Desktop plugin, resolved through the plugin - * registry rather than an import: restarting its guacd WebSocket server after - * an admin edits the guacd URL setting. - * - * The plugin publishes "remote-desktop.sessions" through ctx.registry on - * activate and the registry revokes it on deactivate. A restart while it is - * off does nothing, because resurrecting a server the plugin does not own - * would be worse than doing nothing. - */ - -import { consume } from "../plugins/registry.js"; - -export const GUACAMOLE_SESSIONS_KEY = "remote-desktop.sessions"; - -export interface GuacamoleSessionProvider { - restart: () => Promise; -} - -/** Restarts the plugin's guacd WebSocket server. A no-op while it is off. */ -export async function restartGuacamoleService(): Promise { - await consume(GUACAMOLE_SESSIONS_KEY)?.restart(); -} diff --git a/src/backend/plugins/ctx-credentials.ts b/src/backend/plugins/ctx-credentials.ts new file mode 100644 index 000000000..41cdb2407 --- /dev/null +++ b/src/backend/plugins/ctx-credentials.ts @@ -0,0 +1,233 @@ +/** + * ctx.credentials: plaintext protocol credentials for a host, for a plugin + * that hands them to a program core does not run (remote desktop gives them + * to guacd). Needs credentials:read, the one critical capability, and every + * call is audited whether it succeeds or not. + * + * The protocol credentials themselves stay in core, next to the host, because + * core's sharing model (per-recipient secret snapshots, personal overrides) + * is what decides which ones a shared recipient may use. + */ + +import type { + PluginCredentials, + PluginHostProtocol, + PluginProtocolTarget, +} from "@termix/plugin-sdk/backend"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import { assertCapability } from "./permissions.js"; +import { getActor } from "./actor.js"; + +type AuditFn = ( + action: string, + details: string, + outcome: { success: boolean; errorMessage?: string }, +) => Promise; + +interface Deps { + manifest: PluginManifest; + audit: AuditFn; +} + +const PROTOCOLS: PluginHostProtocol[] = ["rdp", "vnc", "telnet"]; + +type HostRow = Record; + +function str(value: unknown): string { + return typeof value === "string" ? value : ""; +} + +function parseJumpHosts(value: unknown): Array<{ hostId: number }> { + let parsed: unknown = value; + if (typeof value === "string") { + try { + parsed = JSON.parse(value); + } catch { + return []; + } + } + if (!Array.isArray(parsed)) return []; + return parsed + .map((hop) => Number((hop as { hostId?: unknown })?.hostId)) + .filter((id) => Number.isInteger(id) && id > 0) + .map((hostId) => ({ hostId })); +} + +async function resolveOwnerAuth( + host: HostRow, + protocol: PluginHostProtocol, +): Promise { + const credentialId = host[`${protocol}CredentialId`] as number | null; + const authType = + str(host[`${protocol}AuthType`]) || + (credentialId ? "credential" : "direct"); + let username = str(host[`${protocol}User`]); + let password = str(host[`${protocol}Password`]); + + if (authType === "credential" && credentialId) { + const { createCurrentHostResolutionRepository } = + await import("../database/repositories/factory.js"); + const credential = + await createCurrentHostResolutionRepository().findCredentialByIdForUser( + credentialId, + host.userId as string, + ); + // The domain never comes from a stored credential. + if (credential?.username) username = credential.username; + if (credential?.password) password = credential.password; + } + + if (protocol === "rdp") username ||= str(host.username); + password ||= str(host.password); + + return { + authType, + username, + password, + domain: str(host.rdpDomain) || str(host.domain), + }; +} + +async function resolveRecipientAuth( + host: HostRow, + hostId: number, + userId: string, + protocol: PluginHostProtocol, +): Promise { + const { resolveRecipientSharedHostAuthentication } = + await import("../utils/shared-host-auth-resolver.js"); + // Recipients never read the owner's raw secrets, so start from nothing. + const recipientHost = { + ...host, + password: null, + rdpUser: null, + rdpPassword: null, + vncUser: null, + vncPassword: null, + telnetUser: null, + telnetPassword: null, + }; + let resolution: Awaited< + ReturnType + >; + try { + resolution = await resolveRecipientSharedHostAuthentication( + recipientHost as never, + hostId, + userId, + protocol, + ); + } catch { + // Same as before the move: connect without stored credentials. + resolution = { source: "required" }; + } + + const auth = + resolution.source === "personal-override" + ? { ...resolution.credential, domain: null } + : resolution.source === "owner-shared" + ? resolution.secret + : null; + + let authType = "direct"; + if (resolution.source === "personal-override") authType = "credential"; + else if (resolution.source === "owner-shared") authType = resolution.authType; + else if ( + resolution.source === "secretless" && + str(host[`${protocol}AuthType`]) === "none" + ) { + authType = "none"; + } + + return { + authType, + username: str(auth?.username), + password: str(auth?.password), + domain: str(auth?.domain) || str(host.rdpDomain) || str(host.domain), + }; +} + +export function createPluginCredentials({ + manifest, + audit, +}: Deps): PluginCredentials { + const pluginId = manifest.id; + const declared = manifest.capabilities; + + return { + resolveHostProtocol: async (hostId, protocol) => { + const details = `${protocol} credentials for host ${hostId}`; + try { + await assertCapability(pluginId, "credentials:read", declared); + if (!PROTOCOLS.includes(protocol)) { + throw new Error(`Unsupported protocol: ${String(protocol)}`); + } + } catch (error) { + await audit("credentials_read", details, { + success: false, + errorMessage: error instanceof Error ? error.message : String(error), + }); + throw error; + } + + const userId = getActor(); + if (!userId) { + throw new Error( + "ctx.credentials needs an acting user: call it inside a request or ctx.asUser", + ); + } + + const { createCurrentHostResolutionRepository } = + await import("../database/repositories/factory.js"); + const repository = createCurrentHostResolutionRepository(); + // Shared hosts carry fields encrypted under the owner's key. + const ownerId = await repository.findHostOwnerId(hostId); + const host = ownerId + ? ((await repository.findHostById(hostId, ownerId)) as HostRow | null) + : null; + if (!host) { + await audit("credentials_read", details, { + success: false, + errorMessage: "Host not found", + }); + return null; + } + + const shared = host.userId !== userId; + if (shared) { + const { PermissionManager } = + await import("../utils/permission-manager.js"); + const access = await PermissionManager.getInstance().canAccessHost( + userId, + hostId, + "connect", + ); + if (!access.hasAccess) { + await audit("credentials_read", details, { + success: false, + errorMessage: "No connect access", + }); + return null; + } + } + + const auth = shared + ? await resolveRecipientAuth(host, hostId, userId, protocol) + : await resolveOwnerAuth(host, protocol); + + await audit("credentials_read", details, { success: true }); + return { + host: { + id: hostId, + name: (host.name as string | null) ?? null, + ip: str(host.ip), + port: Number(host.port) || 0, + ownerUserId: host.userId as string, + jumpHosts: parseJumpHosts(host.jumpHosts), + }, + shared, + auth, + }; + }, + }; +} diff --git a/src/backend/plugins/ctx-hosts.ts b/src/backend/plugins/ctx-hosts.ts index 87c9012c1..a56b92bfa 100644 --- a/src/backend/plugins/ctx-hosts.ts +++ b/src/backend/plugins/ctx-hosts.ts @@ -78,7 +78,6 @@ function toRecord(host: Record): PluginHostRecord { folder: (host.folder as string | null) ?? null, jumpHosts: host.jumpHosts, enableSsh: (host.enableSsh as boolean | null) ?? null, - enableRdp: (host.enableRdp as boolean | null) ?? null, enableTerminal: (host.enableTerminal as boolean | null) ?? null, enableFileManager: (host.enableFileManager as boolean | null) ?? null, enableTunnel: (host.enableTunnel as boolean | null) ?? null, @@ -205,6 +204,14 @@ export function createPluginHosts({ manifest, audit }: Deps): PluginHosts { ...host, userId, }); + // Fields another plugin owns (a remote desktop switch) go to that + // plugin's host settings, the same way a bulk import row's do. + const { applyPluginHostImportSettings } = + await import("../database/routes/host-plugin-settings.js"); + await applyPluginHostImportSettings( + created.id, + host as Record, + ); await audit("hosts_create", `host ${created.id}`, { success: true }); return toRecord(created as unknown as Record); }, diff --git a/src/backend/plugins/ctx.ts b/src/backend/plugins/ctx.ts index 7b05e5f98..bb3d1b2dd 100644 --- a/src/backend/plugins/ctx.ts +++ b/src/backend/plugins/ctx.ts @@ -43,6 +43,7 @@ import { type PluginContext, type PluginModule, type PluginOpenIsolatedWindowRequest, + type PluginNativeRdpRequest, } from "@termix/plugin-sdk/backend"; import type { PluginTableDefinition } from "@termix/plugin-sdk/db"; import * as syncRegistry from "./sync-registry.js"; @@ -52,6 +53,8 @@ import { } from "../database/db/dialect.js"; import { createPluginAuth, createPluginSsh } from "./ctx-ssh-auth.js"; import { createPluginHosts } from "./ctx-hosts.js"; +import { createPluginCredentials } from "./ctx-credentials.js"; +import { isElectronIpcAvailable } from "../utils/electron-ipc-bridge.js"; export type { PluginModule }; @@ -336,6 +339,28 @@ export function createPluginContext( }, ); + const desktopLaunchNativeRdp = guarded( + manifest, + "desktop:window", + async (request: PluginNativeRdpRequest) => { + const { isElectronIpcAvailable, requestFromElectronMain } = + await import("../utils/electron-ipc-bridge.js"); + if (!isElectronIpcAvailable()) { + throw new Error( + `Plugin ${pluginId} tried to open the native RDP client outside the desktop app`, + ); + } + return requestFromElectronMain<{ success: boolean; error?: string }>( + "launch-native-rdp", + request, + ); + }, + { + action: "desktop_launch_native_rdp", + details: () => "opened the native RDP client", + }, + ); + const capabilitiesRequire = async (capability: string) => { try { await assertCapability(pluginId, capability, declared); @@ -696,8 +721,12 @@ export function createPluginContext( desktop: { openIsolatedWindow: (request) => desktopOpenIsolatedWindow(request), + launchNativeRdp: (request) => desktopLaunchNativeRdp(request), + available: () => isElectronIpcAvailable(), }, + credentials: createPluginCredentials({ manifest, audit: auditCall }), + audit: { // Attribution comes from the runtime: the actor, never a plugin value. record: async (entry) => { diff --git a/src/backend/plugins/settings.ts b/src/backend/plugins/settings.ts index bc9275ff2..a7386a391 100644 --- a/src/backend/plugins/settings.ts +++ b/src/backend/plugins/settings.ts @@ -40,7 +40,6 @@ export type { PluginSettingsScope }; */ export const CORE_SETTINGS_ALLOWLIST: readonly string[] = [ "app_name", - "guac_enabled", "global_metrics_interval", "global_status_check_interval", ]; diff --git a/src/backend/starter.ts b/src/backend/starter.ts index a1e8784a7..d81b035ad 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -377,6 +377,10 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { const { runSessionRecordingSettingsMigration } = await import("./utils/crypto-migration/session-recording-settings-migration.js"); await runSessionRecordingSettingsMigration(); + + const { runRemoteDesktopSettingsMigration } = + await import("./utils/crypto-migration/remote-desktop-settings-migration.js"); + await runRemoteDesktopSettingsMigration(); } catch (error) { systemLogger.warn("Plugin runtime failed to initialize", { operation: "plugin_init", diff --git a/src/backend/tests/database/routes/host-normalizers.test.ts b/src/backend/tests/database/routes/host-normalizers.test.ts index f9ba1f89d..23e35cbfe 100644 --- a/src/backend/tests/database/routes/host-normalizers.test.ts +++ b/src/backend/tests/database/routes/host-normalizers.test.ts @@ -105,15 +105,10 @@ describe("normalizeProtocolEnableFields", () => { expect(normalizeProtocolEnableFields({ name: "server" })).toEqual({}); }); - it("converts explicitly provided protocol booleans to database integers", () => { + it("converts an explicitly provided SSH switch to a database integer", () => { expect( - normalizeProtocolEnableFields({ - enableSsh: true, - enableRdp: false, - enableVnc: undefined, - enableTelnet: true, - }), - ).toEqual({ enableSsh: 1, enableRdp: 0, enableTelnet: 1 }); + normalizeProtocolEnableFields({ enableSsh: true, enableRdp: false }), + ).toEqual({ enableSsh: 1 }); }); }); @@ -174,14 +169,13 @@ describe("normalizeImportedHost", () => { expect(host.connectionType).toBe("ssh"); expect(host.port).toBe(22); expect(host.enableSsh).toBe(true); - expect(host.enableRdp).toBe(false); }); it("infers rdp from enableRdp and uses default rdp port", () => { const host = normalizeImportedHost({ enableRdp: true, ip: "10.0.0.2" }); expect(host.connectionType).toBe("rdp"); expect(host.port).toBe(3389); - expect(host.enableRdp).toBe(true); + expect(host.enableSsh).toBe(false); }); it("honors an explicit port over protocol defaults", () => { @@ -321,21 +315,15 @@ describe("transformHostResponse", () => { expect(result.jumpHosts).toEqual([]); }); - it("infers protocol flags for a migrated non-ssh host", () => { - const result = transformHostResponse({ - connectionType: "rdp", - enableSsh: true, - }); - expect(result.enableSsh).toBe(false); - expect(result.enableRdp).toBe(true); + it("passes the stored SSH switch through", () => { + // The remote desktop migration corrects connection_type-only hosts once. + expect(transformHostResponse({ enableSsh: false }).enableSsh).toBe(false); }); - it("applies default protocol ports", () => { + it("applies the default SSH port", () => { const result = transformHostResponse({ port: 22 }); expect(result.sshPort).toBe(22); - expect(result.rdpPort).toBe(3389); - expect(result.vncPort).toBe(5900); - expect(result.telnetPort).toBe(23); + expect(result).not.toHaveProperty("rdpPort"); }); }); @@ -426,8 +414,6 @@ describe("sanitizeHostForRecipient", () => { ); expect(result.name).toBe("prod"); expect(result.ip).toBe("10.0.0.42"); - expect(result.enableRdp).toBe(true); - expect(result.rdpPort).toBe(3389); expect(result.rdpAuthType).toBe("none"); expect(result.permissionLevel).toBe("connect"); expect(result.shareSshAuth).toBe(true); diff --git a/plugins/remote-desktop/tests/frontend/native-rdp.test.ts b/src/backend/tests/electron/native-rdp.test.ts similarity index 100% rename from plugins/remote-desktop/tests/frontend/native-rdp.test.ts rename to src/backend/tests/electron/native-rdp.test.ts diff --git a/src/backend/tests/plugins/ctx-credentials.test.ts b/src/backend/tests/plugins/ctx-credentials.test.ts new file mode 100644 index 000000000..a64d52da5 --- /dev/null +++ b/src/backend/tests/plugins/ctx-credentials.test.ts @@ -0,0 +1,228 @@ +/** + * ctx.credentials.resolveHostProtocol: credentials:read is checked and every + * call audited, the owner gets their stored or credential-mode login, and a + * shared recipient only ever sees what core's sharing rules hand them. + */ + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const grants = new Map(); +const auditEntries: Array> = []; + +const state = vi.hoisted(() => ({ + hosts: new Map>(), + credentials: new Map(), + canConnect: true, + resolution: null as Record | null, + resolverCalls: [] as Array>, +})); + +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentPluginPermissionGrantRepository: () => ({ + listByPlugin: async (pluginId: string) => + (grants.get(pluginId) ?? []).map((capability) => ({ + pluginId, + capability, + })), + }), + createCurrentHostResolutionRepository: () => ({ + findHostOwnerId: async (hostId: number) => + (state.hosts.get(hostId)?.userId as string) ?? null, + findHostById: async (hostId: number) => state.hosts.get(hostId) ?? null, + findCredentialByIdForUser: async (id: number) => + state.credentials.get(id) ?? null, + }), +})); + +vi.mock("../../utils/audit-logger.js", () => ({ + logAudit: async (entry: Record) => { + auditEntries.push(entry); + }, +})); + +vi.mock("../../utils/permission-manager.js", () => ({ + PermissionManager: { + getInstance: () => ({ + canAccessHost: async () => ({ hasAccess: state.canConnect }), + }), + }, +})); + +vi.mock("../../utils/shared-host-auth-resolver.js", () => ({ + resolveRecipientSharedHostAuthentication: async ( + host: Record, + ) => { + state.resolverCalls.push(host); + return state.resolution; + }, +})); + +import { createPluginContext, createPluginHandle } from "../../plugins/ctx.js"; +import { invalidatePluginPermissionCache } from "../../plugins/permissions.js"; +import { runAsActor } from "../../plugins/actor.js"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; + +function contextFor(capabilities: string[]) { + const manifest = { + id: "demo", + name: "demo", + version: "1.0.0", + description: "", + author: { name: "test" }, + license: "MIT", + category: "Productivity", + engine: { termix: ">=2.9.0", api: "1" }, + capabilities, + } as PluginManifest; + const handle = createPluginHandle("demo", { activate: () => {} }); + return createPluginContext(manifest, handle); +} + +const baseHost = { + id: 7, + userId: "owner", + name: "Win box", + ip: "10.0.0.7", + port: 22, + username: "sshuser", + password: "ssh-secret", + jumpHosts: JSON.stringify([{ hostId: 3 }]), + rdpUser: "admin", + rdpPassword: "rdp-secret", + rdpDomain: "CORP", + rdpAuthType: null, + rdpCredentialId: null, +}; + +beforeEach(() => { + grants.clear(); + auditEntries.length = 0; + invalidatePluginPermissionCache(); + state.hosts.clear(); + state.credentials.clear(); + state.canConnect = true; + state.resolution = null; + state.resolverCalls.length = 0; + state.hosts.set(7, { ...baseHost }); +}); + +describe("ctx.credentials.resolveHostProtocol", () => { + it("refuses without credentials:read and audits the refusal", async () => { + grants.set("demo", []); + const ctx = contextFor([]); + + await expect( + runAsActor("owner", "request", () => + ctx.credentials.resolveHostProtocol(7, "rdp"), + ), + ).rejects.toThrow(/credentials:read/); + expect(auditEntries.at(-1)).toMatchObject({ + action: "plugin_credentials_read", + success: false, + }); + }); + + it("returns the owner's stored login and audits it", async () => { + grants.set("demo", ["credentials:read"]); + const ctx = contextFor(["credentials:read"]); + + const target = await runAsActor("owner", "request", () => + ctx.credentials.resolveHostProtocol(7, "rdp"), + ); + expect(target).toEqual({ + host: { + id: 7, + name: "Win box", + ip: "10.0.0.7", + port: 22, + ownerUserId: "owner", + jumpHosts: [{ hostId: 3 }], + }, + shared: false, + auth: { + authType: "direct", + username: "admin", + password: "rdp-secret", + domain: "CORP", + }, + }); + expect(auditEntries.at(-1)).toMatchObject({ + action: "plugin_credentials_read", + success: true, + }); + }); + + it("uses the stored credential in credential mode", async () => { + grants.set("demo", ["credentials:read"]); + state.hosts.set(7, { + ...baseHost, + vncCredentialId: 11, + vncAuthType: "credential", + }); + state.credentials.set(11, { username: "viewer", password: "vnc-pass" }); + const ctx = contextFor(["credentials:read"]); + + const target = await runAsActor("owner", "request", () => + ctx.credentials.resolveHostProtocol(7, "vnc"), + ); + expect(target?.auth).toMatchObject({ + authType: "credential", + username: "viewer", + password: "vnc-pass", + }); + }); + + it("never hands a shared recipient the owner's raw secrets", async () => { + grants.set("demo", ["credentials:read"]); + state.resolution = { + source: "owner-shared", + authType: "direct", + secret: { username: "shared", password: "snap", domain: null }, + }; + const ctx = contextFor(["credentials:read"]); + + const target = await runAsActor("guest", "request", () => + ctx.credentials.resolveHostProtocol(7, "rdp"), + ); + expect(target?.shared).toBe(true); + expect(target?.auth).toMatchObject({ + username: "shared", + password: "snap", + }); + expect(state.resolverCalls[0]).toMatchObject({ + password: null, + rdpUser: null, + rdpPassword: null, + }); + }); + + it("gives a recipient with nothing shared an empty login", async () => { + grants.set("demo", ["credentials:read"]); + state.resolution = { source: "required" }; + const ctx = contextFor(["credentials:read"]); + + const target = await runAsActor("guest", "request", () => + ctx.credentials.resolveHostProtocol(7, "telnet"), + ); + expect(target?.auth.username).toBe(""); + expect(target?.auth.password).toBe(""); + }); + + it("returns null without connect access or for a missing host", async () => { + grants.set("demo", ["credentials:read"]); + state.canConnect = false; + const ctx = contextFor(["credentials:read"]); + + await expect( + runAsActor("guest", "request", () => + ctx.credentials.resolveHostProtocol(7, "rdp"), + ), + ).resolves.toBeNull(); + await expect( + runAsActor("owner", "request", () => + ctx.credentials.resolveHostProtocol(99, "rdp"), + ), + ).resolves.toBeNull(); + expect(auditEntries.at(-1)).toMatchObject({ success: false }); + }); +}); diff --git a/src/backend/tests/plugins/ctx-desktop.test.ts b/src/backend/tests/plugins/ctx-desktop.test.ts index 54c3b5692..f1b6513b1 100644 --- a/src/backend/tests/plugins/ctx-desktop.test.ts +++ b/src/backend/tests/plugins/ctx-desktop.test.ts @@ -121,3 +121,50 @@ describe("ctx.desktop.openIsolatedWindow", () => { }); }); }); + +describe("ctx.desktop.launchNativeRdp", () => { + const request = { host: "win.example.test", port: 3389, username: "bob" }; + + it("refuses without desktop:window", async () => { + grants.set("demo", []); + const { ctx } = contextFor("demo", []); + + await expect(ctx.desktop.launchNativeRdp(request)).rejects.toThrow( + /desktop:window/, + ); + expect(bridge.requestFromElectronMain).not.toHaveBeenCalled(); + expect(auditEntries.at(-1)).toMatchObject({ + action: "plugin_desktop_launch_native_rdp", + success: false, + }); + }); + + it("refuses outside the desktop app", async () => { + grants.set("demo", ["desktop:window"]); + const { ctx } = contextFor("demo", ["desktop:window"]); + + expect(ctx.desktop.available()).toBe(false); + await expect(ctx.desktop.launchNativeRdp(request)).rejects.toThrow( + /desktop app/, + ); + }); + + it("relays to Electron main and audits success", async () => { + grants.set("demo", ["desktop:window"]); + bridge.available = true; + const { ctx } = contextFor("demo", ["desktop:window"]); + + expect(ctx.desktop.available()).toBe(true); + await expect(ctx.desktop.launchNativeRdp(request)).resolves.toEqual({ + success: true, + }); + expect(bridge.requestFromElectronMain).toHaveBeenCalledWith( + "launch-native-rdp", + request, + ); + expect(auditEntries.at(-1)).toMatchObject({ + action: "plugin_desktop_launch_native_rdp", + success: true, + }); + }); +}); diff --git a/src/backend/tests/plugins/ctx-hosts.test.ts b/src/backend/tests/plugins/ctx-hosts.test.ts index 416328fbc..a179b156e 100644 --- a/src/backend/tests/plugins/ctx-hosts.test.ts +++ b/src/backend/tests/plugins/ctx-hosts.test.ts @@ -21,6 +21,7 @@ const h = vi.hoisted(() => ({ activities: [] as Array>, sessions: [] as number[], released: [] as number[], + importRows: [] as Array<{ hostId: number; row: Record }>, roles: [] as Array<{ id: number; name: string; @@ -110,6 +111,14 @@ vi.mock("../../hosts/host-session-status.js", () => ({ }, }, })); +vi.mock("../../database/routes/host-plugin-settings.js", () => ({ + applyPluginHostImportSettings: async ( + hostId: number, + row: Record, + ) => { + h.importRows.push({ hostId, row }); + }, +})); vi.mock("../../utils/shared-host-secrets-manager.js", () => ({ SharedHostSecretsManager: { getInstance: () => ({ @@ -325,6 +334,7 @@ describe("ctx.hosts", () => { port: 22, username: "root", authType: "password", + enableRdp: true, }); expect(created).toMatchObject({ id: 99, @@ -332,6 +342,11 @@ describe("ctx.hosts", () => { userId: "user-1", }); expect(h.created).toHaveLength(1); + // Another plugin's fields go through its import normalizer. + expect(h.importRows.at(-1)).toMatchObject({ + hostId: 99, + row: { enableRdp: true }, + }); }); it("updates a host once granted hosts:write", async () => { diff --git a/src/backend/tests/plugins/settings-manifest.test.ts b/src/backend/tests/plugins/settings-manifest.test.ts index 1b3138b91..68f7b7f70 100644 --- a/src/backend/tests/plugins/settings-manifest.test.ts +++ b/src/backend/tests/plugins/settings-manifest.test.ts @@ -83,6 +83,24 @@ describe("contributes.settings", () => { expect(errors.join(" ")).toContain('Unknown field "wat"'); }); + it("accepts a hidden field and rejects a non-boolean hidden", () => { + expect( + validateManifest( + withSettings({ + host: { + fields: [{ key: "a", type: "number", labelKey: "k", hidden: true }], + }, + }), + ), + ).toEqual([]); + const errors = validateManifest( + withSettings({ + admin: [{ key: "a", type: "string", labelKey: "k", hidden: "yes" }], + }), + ); + expect(errors.join(" ")).toContain("hidden must be a boolean"); + }); + it("rejects an unknown scope", () => { const errors = validateManifest( withSettings({ global: [{ key: "a", type: "string", labelKey: "k" }] }), diff --git a/src/backend/tests/utils/raw-rows.test.ts b/src/backend/tests/utils/raw-rows.test.ts new file mode 100644 index 000000000..1c7790857 --- /dev/null +++ b/src/backend/tests/utils/raw-rows.test.ts @@ -0,0 +1,38 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { sql } from "drizzle-orm"; + +const db = vi.hoisted(() => ({ current: {} as Record })); + +vi.mock("../../database/db/index.js", () => ({ + getDb: () => db.current, +})); + +const { selectRows, runStatement } = + await import("../../utils/crypto-migration/raw-rows.js"); + +describe("raw-rows", () => { + beforeEach(() => { + db.current = {}; + }); + + it("uses all() and run() on SQLite", async () => { + const run = vi.fn(); + db.current = { all: async () => [{ id: 1 }], run }; + await expect(selectRows(sql`SELECT 1`)).resolves.toEqual([{ id: 1 }]); + await runStatement(sql`UPDATE x SET y = 1`); + expect(run).toHaveBeenCalledOnce(); + }); + + it("reads rows from node-postgres's { rows }", async () => { + db.current = { execute: async () => ({ rows: [{ id: 2 }] }) }; + await expect(selectRows(sql`SELECT 1`)).resolves.toEqual([{ id: 2 }]); + }); + + it("reads rows from mysql2's [rows, fields]", async () => { + const execute = vi.fn(async () => [[{ id: 3 }], []]); + db.current = { execute }; + await expect(selectRows(sql`SELECT 1`)).resolves.toEqual([{ id: 3 }]); + await runStatement(sql`UPDATE x SET y = 1`); + expect(execute).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/backend/tests/utils/remote-desktop-settings-migration.test.ts b/src/backend/tests/utils/remote-desktop-settings-migration.test.ts new file mode 100644 index 000000000..273393996 --- /dev/null +++ b/src/backend/tests/utils/remote-desktop-settings-migration.test.ts @@ -0,0 +1,256 @@ +/** + * The Remote Desktop settings migration. + * + * An upgrade must be lossless: the admin switch and guacd address, each + * user's RDP defaults and every host's RDP/VNC/Telnet options must come + * through into the plugin's own settings, whatever shape an old host had. + * Running it twice must not duplicate or clobber 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; +} + +const state = vi.hoisted(() => ({ + coreSettings: new Map(), + pluginRows: [] as Array<{ + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; + }>, + hostRows: [] as Array>, + userRows: [] as Array<{ user_id: string; rdp_defaults: string | null }>, + statements: [] as string[], + pluginInstalled: true, +})); + +const find = (scope: string, scopeId: string | null, key: string) => + state.pluginRows.find( + (row) => row.scope === scope && row.scopeId === scopeId && row.key === key, + ); + +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentPluginRepository: () => ({ + findById: async (id: string) => + state.pluginInstalled && id === "remote-desktop" ? { id } : null, + }), + 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) => + state.pluginRows.filter( + (row) => row.scope === scope && row.scopeId === scopeId, + ), + set: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + value: string | null, + ) => { + const existing = find(scope, scopeId, key); + if (existing) { + existing.value = value; + return; + } + state.pluginRows.push({ pluginId, scope, scopeId, key, value }); + }, + }), + createCurrentSettingsRepository: () => ({ + get: async (key: string) => state.coreSettings.get(key) ?? null, + }), +})); + +vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({ + selectRows: async (query: { queryChunks?: unknown[] }) => { + const text = JSON.stringify(query.queryChunks ?? query); + return text.includes("user_preferences") ? state.userRows : state.hostRows; + }, + runStatement: async (query: unknown) => { + state.statements.push(JSON.stringify(query)); + }, +})); + +vi.mock("../../utils/logger.js", () => ({ + databaseLogger: { info: vi.fn(), warn: vi.fn() }, +})); + +const { runRemoteDesktopSettingsMigration, hostSettingsFromRow } = + await import("../../utils/crypto-migration/remote-desktop-settings-migration.js"); + +const value = (scope: string, scopeId: string | null, key: string) => { + const row = find(scope, scopeId, key) as Row | undefined; + return row?.value == null ? undefined : JSON.parse(row.value); +}; + +function host(overrides: Record) { + return { + id: 1, + port: 22, + connection_type: "ssh", + enable_rdp: 0, + enable_vnc: 0, + enable_telnet: 0, + rdp_port: 3389, + vnc_port: 5900, + telnet_port: 23, + rdp_security: null, + rdp_ignore_cert: 0, + security: null, + ignore_cert: 0, + guacamole_config: null, + enable_terminal_toolbar: 1, + ...overrides, + }; +} + +beforeEach(() => { + state.coreSettings.clear(); + state.pluginRows.length = 0; + state.hostRows.length = 0; + state.userRows.length = 0; + state.statements.length = 0; + state.pluginInstalled = true; +}); + +describe("runRemoteDesktopSettingsMigration", () => { + it("moves the admin switch and guacd address", async () => { + state.coreSettings.set("guac_enabled", "false"); + state.coreSettings.set("guac_url", "guacd:4822"); + + const result = await runRemoteDesktopSettingsMigration(); + + expect(result.moved).toEqual(["guac_enabled", "guac_url"]); + expect(value("admin", null, "enabled")).toBe(false); + expect(value("admin", null, "guacdUrl")).toBe("guacd:4822"); + }); + + it("moves a host's options, reading SQLite's 0 and 1 as booleans", async () => { + state.hostRows.push( + host({ + id: 7, + enable_rdp: 1, + enable_vnc: 0, + rdp_port: 3390, + rdp_security: "nla", + rdp_ignore_cert: 1, + guacamole_config: JSON.stringify({ "color-depth": 24 }), + enable_terminal_toolbar: 0, + }), + ); + + const result = await runRemoteDesktopSettingsMigration(); + + expect(result.hostsMoved).toBe(1); + expect(value("host", "7", "enableRdp")).toBe(true); + expect(value("host", "7", "enableVnc")).toBeUndefined(); + expect(value("host", "7", "rdpPort")).toBe(3390); + expect(value("host", "7", "rdpSecurity")).toBe("nla"); + expect(value("host", "7", "rdpIgnoreCert")).toBe(true); + expect(value("host", "7", "guacamoleConfig")).toEqual({ + "color-depth": 24, + }); + expect(value("host", "7", "enableToolbar")).toBe(false); + }); + + it("leaves a plain SSH host alone", async () => { + state.hostRows.push(host({ id: 3 })); + const result = await runRemoteDesktopSettingsMigration(); + expect(result.hostsMoved).toBe(0); + expect(state.pluginRows).toHaveLength(0); + }); + + it("reads real booleans too, as Postgres returns them", async () => { + state.hostRows.push(host({ id: 8, enable_vnc: true, vnc_port: 5901 })); + await runRemoteDesktopSettingsMigration(); + expect(value("host", "8", "enableVnc")).toBe(true); + expect(value("host", "8", "vncPort")).toBe(5901); + }); + + it("moves each user's RDP defaults", async () => { + state.userRows.push({ + user_id: "u1", + rdp_defaults: JSON.stringify({ + colorDepth: 24, + resizeMethod: "reconnect", + disableCopy: true, + enableWallpaper: false, + }), + }); + state.userRows.push({ user_id: "u2", rdp_defaults: null }); + + const result = await runRemoteDesktopSettingsMigration(); + + expect(result.usersMoved).toBe(1); + expect(value("user", "u1", "colorDepth")).toBe("24"); + expect(value("user", "u1", "resizeMethod")).toBe("reconnect"); + expect(value("user", "u1", "disableCopy")).toBe("on"); + expect(value("user", "u1", "enableWallpaper")).toBe("off"); + }); + + it("changes nothing when run twice", async () => { + state.coreSettings.set("guac_url", "guacd:4822"); + state.hostRows.push(host({ id: 7, enable_rdp: 1, rdp_port: 3390 })); + state.userRows.push({ + user_id: "u1", + rdp_defaults: JSON.stringify({ colorDepth: 16 }), + }); + + await runRemoteDesktopSettingsMigration(); + const firstRun = JSON.stringify(state.pluginRows); + // Changed after the first run: must not be overwritten by the old values. + find("host", "7", "rdpPort")!.value = JSON.stringify(4000); + const second = await runRemoteDesktopSettingsMigration(); + + expect(second.moved).toEqual([]); + expect(second.usersMoved).toBe(0); + expect(second.hostsMoved).toBe(0); + expect(second.hostsSkipped).toBe(1); + expect(value("host", "7", "rdpPort")).toBe(4000); + expect(JSON.parse(firstRun)).toHaveLength(state.pluginRows.length); + }); + + it("does nothing until the plugin row exists", async () => { + state.pluginInstalled = false; + state.coreSettings.set("guac_url", "guacd:4822"); + state.hostRows.push(host({ id: 7, enable_rdp: 1 })); + await runRemoteDesktopSettingsMigration(); + expect(state.pluginRows).toHaveLength(0); + }); +}); + +describe("hostSettingsFromRow", () => { + it("turns a connection_type-only host into its protocol switch and port", () => { + expect( + hostSettingsFromRow( + host({ connection_type: "vnc", port: 5905, vnc_port: null }), + ), + ).toEqual({ enableVnc: true, vncPort: 5905 }); + }); + + it("folds the older security and ignore_cert columns in", () => { + expect( + hostSettingsFromRow( + host({ + enable_rdp: 1, + rdp_security: null, + security: "tls", + rdp_ignore_cert: null, + ignore_cert: 1, + }), + ), + ).toEqual({ enableRdp: true, rdpSecurity: "tls", rdpIgnoreCert: true }); + }); +}); diff --git a/src/backend/utils/crypto-migration/raw-rows.ts b/src/backend/utils/crypto-migration/raw-rows.ts new file mode 100644 index 000000000..8d92fbf98 --- /dev/null +++ b/src/backend/utils/crypto-migration/raw-rows.ts @@ -0,0 +1,34 @@ +/** + * Raw SQL for boot migrations that read columns schema.ts no longer declares. + * + * getDb() is typed as the SQLite database, but on Postgres and MySQL it is + * that engine's drizzle instance, which has execute() instead of all() and + * run(). These two pick whichever exists. + */ + +import type { SQL } from "drizzle-orm"; +import { getDb } from "../../database/db/index.js"; + +type AnyDb = { + all?: (query: SQL) => Promise | unknown[]; + run?: (query: SQL) => unknown; + execute?: (query: SQL) => Promise; +}; + +export async function selectRows(query: SQL): Promise { + const db = getDb() as unknown as AnyDb; + if (typeof db.all === "function") return (await db.all(query)) as T[]; + const result = await db.execute!(query); + // node-postgres returns { rows }, mysql2 returns [rows, fields]. + if (Array.isArray(result)) return result[0] as T[]; + return ((result as { rows?: unknown[] }).rows ?? []) as T[]; +} + +export async function runStatement(query: SQL): Promise { + const db = getDb() as unknown as AnyDb; + if (typeof db.run === "function") { + await db.run(query); + return; + } + await db.execute!(query); +} diff --git a/src/backend/utils/crypto-migration/remote-desktop-settings-migration.ts b/src/backend/utils/crypto-migration/remote-desktop-settings-migration.ts new file mode 100644 index 000000000..9df2dcd07 --- /dev/null +++ b/src/backend/utils/crypto-migration/remote-desktop-settings-migration.ts @@ -0,0 +1,286 @@ +/** + * Moves Remote Desktop's settings out of core and into the remote-desktop + * plugin's own settings. + * + * Admin keys from `settings`: guac_enabled and guac_url. Per user, the RDP + * defaults JSON in user_preferences.rdp_defaults. Per host, the option + * columns in ssh_data: the three protocol switches and ports, rdp_security, + * rdp_ignore_cert and guacamole_config, folding in the older security, + * ignore_cert and connection_type-only shapes, plus enable_terminal_toolbar, + * which also drove the remote desktop toolbar. + * + * The logins (users, passwords, credential ids, auth types, domain) stay in + * core, next to the host, where sharing needs them. + * + * The columns stay in the database, unused: core's drizzle migrations run + * before this does, so dropping them there would lose the data first. + * + * Idempotent: an admin key already set, or a user or host that already has + * any remote-desktop row, is skipped, so this is safe on every boot. + */ + +import { sql } from "drizzle-orm"; +import { databaseLogger } from "../logger.js"; +import { + createCurrentPluginRepository, + createCurrentPluginSettingsRepository, + createCurrentSettingsRepository, +} from "../../database/repositories/factory.js"; +import { runStatement, selectRows } from "./raw-rows.js"; + +const PLUGIN_ID = "remote-desktop"; +const PROTOCOLS = ["rdp", "vnc", "telnet"] as const; +const DEFAULT_PORTS = { rdp: 3389, vnc: 5900, telnet: 23 }; + +type Flag = number | boolean | string | null | undefined; + +/** true, 1 and "1" are on; null means the column was never set. */ +function flag(value: Flag): boolean | undefined { + if (value === null || value === undefined) return undefined; + return value === true || value === 1 || value === "1" || value === "true"; +} + +function port(value: unknown): number | undefined { + const parsed = Number(value); + return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 + ? parsed + : undefined; +} + +function jsonObject(value: unknown): Record | undefined { + if (typeof value !== "string" || !value) return undefined; + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === "object" && !Array.isArray(parsed) + ? parsed + : undefined; + } catch { + return undefined; + } +} + +interface LegacyHostRow { + id: number; + port: number | null; + connection_type: string | null; + enable_rdp: Flag; + enable_vnc: Flag; + enable_telnet: Flag; + rdp_port: number | null; + vnc_port: number | null; + telnet_port: number | null; + rdp_security: string | null; + rdp_ignore_cert: Flag; + security: string | null; + ignore_cert: Flag; + guacamole_config: string | null; + enable_terminal_toolbar: Flag; +} + +/** The plugin's host settings for one legacy row, defaults left out. */ +export function hostSettingsFromRow( + row: LegacyHostRow, +): Record { + const values: Record = {}; + const flags = { + rdp: flag(row.enable_rdp) === true, + vnc: flag(row.enable_vnc) === true, + telnet: flag(row.enable_telnet) === true, + }; + const legacyType = PROTOCOLS.find((p) => p === row.connection_type); + // Hosts from before the per-protocol switches only had connection_type. + if (!flags.rdp && !flags.vnc && !flags.telnet && legacyType) { + flags[legacyType] = true; + } + const ports = { + rdp: port(row.rdp_port), + vnc: port(row.vnc_port), + telnet: port(row.telnet_port), + }; + if (legacyType && ports[legacyType] === undefined) { + ports[legacyType] = port(row.port); + } + + for (const protocol of PROTOCOLS) { + const key = `enable${protocol[0].toUpperCase()}${protocol.slice(1)}`; + if (flags[protocol]) values[key] = true; + const value = ports[protocol]; + if (value !== undefined && value !== DEFAULT_PORTS[protocol]) { + values[`${protocol}Port`] = value; + } + } + + const security = row.rdp_security || row.security; + if (security) values.rdpSecurity = security; + const ignoreCert = flag(row.rdp_ignore_cert) ?? flag(row.ignore_cert); + if (ignoreCert) values.rdpIgnoreCert = true; + const config = jsonObject(row.guacamole_config); + if (config && Object.keys(config).length > 0) values.guacamoleConfig = config; + if (flag(row.enable_terminal_toolbar) === false) values.enableToolbar = false; + return values; +} + +/** Old camelCase defaults JSON -> the plugin's user settings. */ +export function userSettingsFromDefaults( + defaults: Record, +): Record { + const values: Record = {}; + const depth = Number(defaults.colorDepth); + if ([16, 24, 32].includes(depth)) values.colorDepth = String(depth); + if ( + defaults.resizeMethod === "display-update" || + defaults.resizeMethod === "reconnect" + ) { + values.resizeMethod = defaults.resizeMethod; + } + for (const key of [ + "forceLossless", + "enableWallpaper", + "enableFontSmoothing", + "enableDesktopComposition", + "disableAudio", + "enablePrinting", + "enableDrive", + "disableCopy", + "disablePaste", + ]) { + if (typeof defaults[key] === "boolean") { + values[key] = defaults[key] ? "on" : "off"; + } + } + return values; +} + +export interface RemoteDesktopSettingsMigrationResult { + moved: string[]; + usersMoved: number; + hostsMoved: number; + hostsSkipped: number; +} + +export async function runRemoteDesktopSettingsMigration(): Promise { + const result: RemoteDesktopSettingsMigrationResult = { + moved: [], + usersMoved: 0, + hostsMoved: 0, + hostsSkipped: 0, + }; + + // plugin_settings has a foreign key to plugins: nothing to migrate into + // until the plugin row exists. + try { + const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); + if (!plugin) return result; + } catch { + return result; + } + + const pluginSettings = createCurrentPluginSettingsRepository(); + const write = ( + scope: "admin" | "user" | "host", + scopeId: string | null, + key: string, + value: unknown, + ) => + pluginSettings.set(PLUGIN_ID, scope, scopeId, key, JSON.stringify(value)); + + try { + const settings = createCurrentSettingsRepository(); + const moves: [string, string, (raw: string) => unknown][] = [ + ["guac_enabled", "enabled", (raw) => raw !== "false" && raw !== "0"], + ["guac_url", "guacdUrl", (raw) => raw], + ]; + for (const [legacyKey, field, parse] of moves) { + const legacy = await settings.get(legacyKey); + if (legacy === null || legacy === "") continue; + const existing = await pluginSettings.get( + PLUGIN_ID, + "admin", + null, + field, + ); + if (existing && existing.value !== null) continue; + await write("admin", null, field, parse(legacy)); + result.moved.push(legacyKey); + } + } catch (error) { + databaseLogger.warn("Remote Desktop admin settings migration failed", { + operation: "remote_desktop_settings_migration", + error: error instanceof Error ? error.message : String(error), + }); + } + + try { + const rows = await selectRows<{ + user_id: string; + rdp_defaults: string | null; + }>(sql`SELECT user_id, rdp_defaults FROM user_preferences`); + for (const row of rows) { + const defaults = jsonObject(row.rdp_defaults); + if (!defaults) continue; + const values = userSettingsFromDefaults(defaults); + if (Object.keys(values).length === 0) continue; + const existing = await pluginSettings.getAll( + PLUGIN_ID, + "user", + row.user_id, + ); + if (existing.length > 0) continue; + for (const [key, value] of Object.entries(values)) { + await write("user", row.user_id, key, value); + } + result.usersMoved++; + } + } catch { + // A fresh install never had the column. + } + + try { + // Raw SQL: schema.ts no longer declares these columns. + const rows = await selectRows(sql` + SELECT id, port, connection_type, enable_rdp, enable_vnc, enable_telnet, + rdp_port, vnc_port, telnet_port, rdp_security, rdp_ignore_cert, + security, ignore_cert, guacamole_config, enable_terminal_toolbar + FROM ssh_data + `); + for (const row of rows) { + const values = hostSettingsFromRow(row); + if (Object.keys(values).length === 0) continue; + const scopeId = String(row.id); + const existing = await pluginSettings.getAll(PLUGIN_ID, "host", scopeId); + if (existing.length > 0) { + result.hostsSkipped++; + continue; + } + for (const [key, value] of Object.entries(values)) { + await write("host", scopeId, key, value); + } + result.hostsMoved++; + } + + // Those same connection_type-only hosts kept the enable_ssh default of + // on, which core used to correct on every read. FALSE and NOT read the + // same on SQLite's integers and on real booleans. + await runStatement(sql` + UPDATE ssh_data SET enable_ssh = FALSE + WHERE connection_type IN ('rdp', 'vnc', 'telnet') + AND (enable_rdp IS NULL OR NOT enable_rdp) + AND (enable_vnc IS NULL OR NOT enable_vnc) + AND (enable_telnet IS NULL OR NOT enable_telnet) + `); + } catch { + // A fresh install never had the columns. + } + + if ( + result.moved.length > 0 || + result.usersMoved > 0 || + result.hostsMoved > 0 + ) { + databaseLogger.info( + `Moved ${result.moved.length} Remote Desktop setting(s), ${result.usersMoved} user(s) and ${result.hostsMoved} host(s) into plugin settings`, + { operation: "remote_desktop_settings_migration" }, + ); + } + return result; +} diff --git a/src/backend/utils/shared-host-secrets-manager.ts b/src/backend/utils/shared-host-secrets-manager.ts index 011f74a45..ce3891476 100644 --- a/src/backend/utils/shared-host-secrets-manager.ts +++ b/src/backend/utils/shared-host-secrets-manager.ts @@ -39,22 +39,19 @@ function snapshotRecordId( return `shared-${hostAccessId}-${targetUserId}-${protocol}`; } -// Mirrors the connection-type migration fallback in transformHostResponse(): -// old hosts only set connectionType, the per-protocol enable flags came later. +// SSH follows its own switch. The remote desktop protocols' switches are the +// remote-desktop plugin's host settings, and the host editor clears a +// protocol's login when it is switched off, so a snapshot is taken whenever +// login data is present (see collectProtocolSnapshots). function enabledProtocols( host: HostResolutionHostRecord, ): Record { const ct = host.connectionType; - const rdp = !!host.enableRdp; - const vnc = !!host.enableVnc; - const telnet = !!host.enableTelnet; - const isMigratedNonSsh = !rdp && !vnc && !telnet && !!ct && ct !== "ssh"; - return { - ssh: isMigratedNonSsh ? false : host.enableSsh !== false, - rdp: isMigratedNonSsh ? ct === "rdp" : rdp, - vnc: isMigratedNonSsh ? ct === "vnc" : vnc, - telnet: isMigratedNonSsh ? ct === "telnet" : telnet, + ssh: host.enableSsh !== false && (!ct || ct === "ssh"), + rdp: true, + vnc: true, + telnet: true, }; } diff --git a/src/types/auth-protocols.ts b/src/types/auth-protocols.ts index ffb044d29..0e128473d 100644 --- a/src/types/auth-protocols.ts +++ b/src/types/auth-protocols.ts @@ -8,29 +8,24 @@ export const SUPPORTED_AUTH_OVERRIDE_PROTOCOLS = export const AUTH_PROTOCOL_METADATA = { ssh: { label: "SSH", - enableField: "enableSsh", credentialField: "credentialId", }, rdp: { label: "RDP", - enableField: "enableRdp", credentialField: "rdpCredentialId", }, vnc: { label: "VNC", - enableField: "enableVnc", credentialField: "vncCredentialId", }, telnet: { label: "Telnet", - enableField: "enableTelnet", credentialField: "telnetCredentialId", }, } as const satisfies Record< AuthOverrideProtocol, { label: string; - enableField: "enableSsh" | "enableRdp" | "enableVnc" | "enableTelnet"; credentialField: | "credentialId" | "rdpCredentialId" diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index c56b3f458..4b5fb7ae0 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -50,12 +50,6 @@ export type LocalPathMutationResult = export interface ElectronAPI { getAppVersion: () => Promise; getPlatform: () => Promise; - openNativeRdp: (options: { - host: string; - port?: number; - username?: string; - domain?: string; - }) => Promise<{ success: boolean; error?: string }>; getSetting?: (key: string) => Promise; setSetting?: (key: string, value: string) => Promise; diff --git a/src/types/host-sidebar-preferences.ts b/src/types/host-sidebar-preferences.ts index 9aa1cb854..71590efa2 100644 --- a/src/types/host-sidebar-preferences.ts +++ b/src/types/host-sidebar-preferences.ts @@ -36,7 +36,8 @@ export interface HostSidebarFilterState { status: ("online" | "offline" | "pinned")[]; /** SSH auth type ids; plugins add their own. */ authType: string[]; - protocol: ("ssh" | "rdp" | "vnc" | "telnet")[]; + /** "ssh" or a plugin protocol id. */ + protocol: string[]; features: ("terminal" | "fileManager" | "tunnel" | "docker")[]; tags: string[]; } @@ -93,14 +94,8 @@ const FILTER_STATUS: HostSidebarFilterState["status"] = [ "offline", "pinned", ]; -/** Same shape as a manifest's contributes.auth.sshAuthTypes entries. */ +/** Same shape as a manifest's auth type and protocol ids. */ const AUTH_TYPE_PATTERN = /^[a-z][a-z0-9-]{0,63}$/; -const FILTER_PROTOCOL: HostSidebarFilterState["protocol"] = [ - "ssh", - "rdp", - "vnc", - "telnet", -]; const FILTER_FEATURES: HostSidebarFilterState["features"] = [ "terminal", "fileManager", @@ -174,7 +169,9 @@ export function sanitizeHostSidebarPreferences( authType: sanitizeStringArray(filtersObj.authType).filter((value) => AUTH_TYPE_PATTERN.test(value), ), - protocol: sanitizeEnumArray(filtersObj.protocol, FILTER_PROTOCOL), + protocol: sanitizeStringArray(filtersObj.protocol).filter((value) => + AUTH_TYPE_PATTERN.test(value), + ), features: sanitizeEnumArray(filtersObj.features, FILTER_FEATURES), tags: sanitizeStringArray(filtersObj.tags), }; diff --git a/src/types/index.ts b/src/types/index.ts index 2c927be6f..4900d095f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,3 @@ -import type { GuacamoleConfig } from "./guacamole-config.js"; import type { StatsConfig } from "./stats-widgets.js"; import type { Request } from "express"; import type { RefObject } from "react"; @@ -250,29 +249,19 @@ export type Host = { delay?: number; }>; - connectionType?: "ssh" | "rdp" | "vnc" | "telnet"; + /** "ssh", or the id of the plugin protocol a host without SSH uses. */ + connectionType?: string; domain?: string; - security?: string; - ignoreCert?: boolean; - guacamoleConfig?: string | GuacamoleConfig; dockerConfig?: Record | null; enableWebUi?: boolean; webUiConfig?: WebUiConfig | null; enableSsh?: boolean; - enableRdp?: boolean; - enableVnc?: boolean; - enableTelnet?: boolean; sshPort?: number; - rdpPort?: number; - vncPort?: number; - telnetPort?: number; rdpCredentialId?: number | null; rdpUser?: string; rdpPassword?: string; rdpDomain?: string; - rdpSecurity?: string; - rdpIgnoreCert?: boolean; vncCredentialId?: number | null; vncPassword?: string; vncUser?: string; @@ -415,29 +404,19 @@ export interface HostData { delay?: number; }>; - connectionType?: "ssh" | "rdp" | "vnc" | "telnet"; + /** "ssh", or the id of the plugin protocol a host without SSH uses. */ + connectionType?: string; domain?: string; - security?: string; - ignoreCert?: boolean; - guacamoleConfig?: GuacamoleConfig | null; dockerConfig?: Record | null; enableWebUi?: boolean; webUiConfig?: WebUiConfig | null; enableSsh?: boolean; - enableRdp?: boolean; - enableVnc?: boolean; - enableTelnet?: boolean; sshPort?: number; - rdpPort?: number; - vncPort?: number; - telnetPort?: number; rdpCredentialId?: number | null; rdpUser?: string; rdpPassword?: string; rdpDomain?: string; - rdpSecurity?: string; - rdpIgnoreCert?: boolean; vncCredentialId?: number | null; vncPassword?: string; vncUser?: string; diff --git a/src/types/ui-types.ts b/src/types/ui-types.ts index 0d166d3ee..2add9f67e 100644 --- a/src/types/ui-types.ts +++ b/src/types/ui-types.ts @@ -1,4 +1,3 @@ -import type { GuacamoleConfig } from "./guacamole-config.js"; import type { TerminalConfig } from "./index.js"; import type { StatsConfig } from "./stats-widgets.js"; import type { HostAuthOverrides } from "./auth-protocols.js"; @@ -152,14 +151,8 @@ export type Host = { quickActions: { name: string; snippetId: string }[]; enableSsh: boolean; - enableRdp: boolean; - enableVnc: boolean; - enableTelnet: boolean; sshPort: number; - rdpPort: number; - vncPort: number; - telnetPort: number; rdpAuthType?: "direct" | "credential" | "none"; rdpCredentialId?: string; @@ -167,8 +160,6 @@ export type Host = { rdpPassword?: string; hasRdpPassword?: boolean; domain?: string; - security?: string; - ignoreCert?: boolean; vncAuthType?: "direct" | "credential"; vncCredentialId?: string; @@ -182,7 +173,6 @@ export type Host = { telnetPassword?: string; hasTelnetPassword?: boolean; - guacamoleConfig?: GuacamoleConfig; /** Host-scope plugin settings, keyed by plugin id. Secrets are redacted. */ pluginSettings?: Record>; forceKeyboardInteractive?: boolean; diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index c1303c27c..1d6b7c8cc 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -2497,13 +2497,7 @@ export function AppShell({ enableTerminalToolbar: false, enableAiAssistant: false, enableSsh: false, - enableRdp: false, - enableVnc: false, - enableTelnet: false, sshPort: 22, - rdpPort: 3389, - vncPort: 5900, - telnetPort: 23, serverTunnels: [], quickActions: [], }; diff --git a/src/ui/api/host-metrics-status-api.ts b/src/ui/api/host-metrics-status-api.ts index a3cc13176..9ad3ce9d1 100644 --- a/src/ui/api/host-metrics-status-api.ts +++ b/src/ui/api/host-metrics-status-api.ts @@ -112,7 +112,6 @@ export async function getAllServerStatuses(): Promise< try { const response = await sshHostApi.get("/db/host"); const defaultOrigin = await resolveConnectionOrigin({ - connectionType: "ssh", connectionOrigin: null, }); localHostIds = (response.data || []) diff --git a/src/ui/api/open-tabs-api.ts b/src/ui/api/open-tabs-api.ts index d78162a2a..b4a594d24 100644 --- a/src/ui/api/open-tabs-api.ts +++ b/src/ui/api/open-tabs-api.ts @@ -146,7 +146,6 @@ export interface UserPreferences { customThemes?: string | null; customKeybindings?: string | null; terminalDefaults?: string | null; - rdpDefaults?: string | null; terminalMacros?: string | null; } diff --git a/src/ui/api/plugins-api.ts b/src/ui/api/plugins-api.ts index ffb526626..3d8ccbde0 100644 --- a/src/ui/api/plugins-api.ts +++ b/src/ui/api/plugins-api.ts @@ -32,6 +32,7 @@ export interface PluginSettingsField { permission?: string; group?: string; component?: string; + hidden?: boolean; } export interface PluginHostSettingsContribution { diff --git a/src/ui/api/settings-api.ts b/src/ui/api/settings-api.ts index 16525d10d..ea017d716 100644 --- a/src/ui/api/settings-api.ts +++ b/src/ui/api/settings-api.ts @@ -106,33 +106,6 @@ export async function updateSessionTimeout( // Tailscale settings/device API wrappers moved to // plugins/tailscale/src/frontend/tailscale-api.ts. -// ============================================================================ -// GUACAMOLE SETTINGS -// ============================================================================ - -export async function getGuacamoleSettings(): Promise<{ - enabled: boolean; - url: string; -}> { - try { - const response = await authApi.get("/users/guacamole-settings"); - return response.data; - } catch (error) { - handleApiError(error, "fetch guacamole settings"); - } -} - -export async function updateGuacamoleSettings(settings: { - enabled?: boolean; - url?: string; -}): Promise { - try { - await authApi.patch("/users/guacamole-settings", settings); - } catch (error) { - handleApiError(error, "update guacamole settings"); - } -} - // ============================================================================ // ANALYTICS SETTINGS // ============================================================================ diff --git a/src/ui/contexts/ConnectionDefaultsContext.tsx b/src/ui/contexts/ConnectionDefaultsContext.tsx index 2b8f24b1b..e9c7478e0 100644 --- a/src/ui/contexts/ConnectionDefaultsContext.tsx +++ b/src/ui/contexts/ConnectionDefaultsContext.tsx @@ -10,27 +10,21 @@ import { } from "react"; import { getUserPreferences, saveUserPreferences } from "@/api/open-tabs-api"; import { - parseRemoteDesktopDefaults, parseTerminalDefaults, - type RemoteDesktopDefaults, type TerminalDefaults, } from "@/lib/connection-defaults"; interface ConnectionDefaultsContextValue { ready: boolean; terminal: TerminalDefaults; - rdp: RemoteDesktopDefaults; saveTerminalDefaults: (value: TerminalDefaults) => Promise; - saveRdpDefaults: (value: RemoteDesktopDefaults) => Promise; } const ConnectionDefaultsContext = createContext( { ready: true, terminal: {}, - rdp: {}, saveTerminalDefaults: async () => {}, - saveRdpDefaults: async () => {}, }, ); @@ -41,7 +35,6 @@ export function ConnectionDefaultsProvider({ }) { const [ready, setReady] = useState(false); const [terminal, setTerminal] = useState({}); - const [rdp, setRdp] = useState({}); useEffect(() => { let cancelled = false; @@ -49,7 +42,6 @@ export function ConnectionDefaultsProvider({ .then((preferences) => { if (cancelled) return; setTerminal(parseTerminalDefaults(preferences.terminalDefaults)); - setRdp(parseRemoteDesktopDefaults(preferences.rdpDefaults)); }) .catch(() => {}) .finally(() => { @@ -64,14 +56,10 @@ export function ConnectionDefaultsProvider({ await saveUserPreferences({ terminalDefaults: JSON.stringify(value) }); setTerminal(value); }, []); - const saveRdpDefaults = useCallback(async (value: RemoteDesktopDefaults) => { - await saveUserPreferences({ rdpDefaults: JSON.stringify(value) }); - setRdp(value); - }, []); const value = useMemo( - () => ({ ready, terminal, rdp, saveTerminalDefaults, saveRdpDefaults }), - [ready, terminal, rdp, saveTerminalDefaults, saveRdpDefaults], + () => ({ ready, terminal, saveTerminalDefaults }), + [ready, terminal, saveTerminalDefaults], ); return ( diff --git a/src/ui/dashboard/DashboardTab.tsx b/src/ui/dashboard/DashboardTab.tsx index cdcfa07a9..bbd0e7b18 100644 --- a/src/ui/dashboard/DashboardTab.tsx +++ b/src/ui/dashboard/DashboardTab.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useCallback, useEffect } from "react"; +import { enabledHostProtocols, protocolPort } from "@/sidebar/host-protocols"; import { useSshAuthProviders } from "@/hooks/useSshAuthProviders"; import { useIsMobile } from "@/hooks/use-mobile"; import { Button } from "@/components/button"; @@ -316,15 +317,12 @@ function QuickActionsCard({ const { t } = useTranslation(); const pinnedHosts = hosts.filter((h) => h.pin); const getConnectionEndpoint = (host: Host) => { + const protocol = host.enableSsh ? undefined : enabledHostProtocols(host)[0]; const port = host.enableSsh ? host.sshPort - : host.enableRdp - ? host.rdpPort - : host.enableVnc - ? host.vncPort - : host.enableTelnet - ? host.telnetPort - : host.port; + : protocol + ? protocolPort(host.pluginSettings, protocol) + : host.port; return `${host.ip}:${port ?? host.port}`; }; const renderConnectionIcon = (host: Host) => { diff --git a/src/ui/lib/connection-defaults.ts b/src/ui/lib/connection-defaults.ts index ff9246541..d1bd30dc9 100644 --- a/src/ui/lib/connection-defaults.ts +++ b/src/ui/lib/connection-defaults.ts @@ -1,4 +1,3 @@ -import type { GuacamoleConfig } from "@/types/guacamole-config"; import type { TerminalConfig } from "@/types"; export type TerminalDefaults = Partial< @@ -20,8 +19,6 @@ export type TerminalDefaults = Partial< > >; -export type RemoteDesktopDefaults = Partial; - function parseObject(value?: string | null): Partial { if (!value) return {}; try { @@ -38,12 +35,6 @@ export function parseTerminalDefaults(value?: string | null): TerminalDefaults { return parseObject(value); } -export function parseRemoteDesktopDefaults( - value?: string | null, -): RemoteDesktopDefaults { - return parseObject(value); -} - // Booleans have three states in the defaults UI: unset means the host decides. export type TriState = "inherit" | "on" | "off"; diff --git a/src/ui/lib/connection-origin.ts b/src/ui/lib/connection-origin.ts index 5296792c6..aaaabd0c8 100644 --- a/src/ui/lib/connection-origin.ts +++ b/src/ui/lib/connection-origin.ts @@ -4,12 +4,9 @@ import { websocketAuthProtocols } from "@/lib/ws-auth"; export type ConnectionOrigin = "local" | "remote"; interface OriginResolvableHost { - connectionType?: string | null; connectionOrigin?: ConnectionOrigin | null; } -const GUACAMOLE_CONNECTION_TYPES = new Set(["rdp", "vnc", "telnet"]); - /** * Resolves which backend a given host's interactive connection (SSH, Docker * console, RDP/VNC/Telnet) should dial: the desktop app's embedded local @@ -21,16 +18,16 @@ const GUACAMOLE_CONNECTION_TYPES = new Set(["rdp", "vnc", "telnet"]); * Everything else follows the host's own override if set, falling back to * the desktop-wide default. * - * RDP/VNC/Telnet are the exception to that fallback: left on Default they - * resolve to "remote" rather than following the desktop-wide setting. They - * need a guacd, which the desktop does not ship, so originating them here - * only works once the user has pointed Termix at one of their own (the - * global guacd URL setting, or a host's guacd Proxy override). Making that - * opt-in per host keeps an upgrade from moving working connections onto a - * guacd that isn't there -- see Termix-SSH/Support#1240. + * `defaultRemote` makes a host left on Default resolve to "remote" instead of + * following the desktop-wide setting. Remote desktop passes it: it needs a + * guacd, which the desktop does not ship, so originating it here only works + * once the user has pointed Termix at one of their own. Making that opt-in + * per host keeps an upgrade from moving working connections onto a guacd + * that isn't there -- see Termix-SSH/Support#1240. */ export async function resolveConnectionOrigin( host: OriginResolvableHost, + options: { defaultRemote?: boolean } = {}, ): Promise { if (!isElectron()) { return "local"; @@ -38,7 +35,7 @@ export async function resolveConnectionOrigin( if (host.connectionOrigin === "local" || host.connectionOrigin === "remote") { return host.connectionOrigin; } - if (GUACAMOLE_CONNECTION_TYPES.has(host.connectionType ?? "")) { + if (options.defaultRemote) { return "remote"; } diff --git a/src/ui/lib/plugin-transport.ts b/src/ui/lib/plugin-transport.ts index 718e7a638..b0f10b78c 100644 --- a/src/ui/lib/plugin-transport.ts +++ b/src/ui/lib/plugin-transport.ts @@ -17,7 +17,7 @@ */ import type { AxiosInstance } from "axios"; -import { authApi } from "@/main-axios"; +import { authApi, createRemoteOriginApiInstance } from "@/main-axios"; import { getBasePath } from "@/lib/base-path"; import { isElectron } from "@/lib/electron"; import { websocketAuthProtocols } from "@/lib/ws-auth"; @@ -75,6 +75,27 @@ export function createPluginApi(pluginId: string): AxiosInstance { }) as AxiosInstance; } +const remotePluginApis = new Map(); + +/** + * The same client pointed at the connected remote server, for a desktop app + * host whose connection origin is "remote". Outside Electron, and for + * "local", it is the ordinary client. + */ +export function pluginApiFor( + pluginId: string, + origin: ConnectionOrigin | undefined, + local: AxiosInstance, +): AxiosInstance { + if (origin !== "remote" || !isElectron()) return local; + let client = remotePluginApis.get(pluginId); + if (!client) { + client = createRemoteOriginApiInstance(pluginApiPath(pluginId)); + remotePluginApis.set(pluginId, client); + } + return client; +} + /** * pluginWsUrl for a path a backend handed out ("/plugin-ws//?q"), * so a caller can dial a plugin's socket without naming the plugin. diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 45c6185c1..af4c29378 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -725,7 +725,6 @@ "connectionDetails": "Connection Details", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Remote Desktop", "port": "Port", "username": "Username", "folder": "Folder", @@ -752,10 +751,7 @@ "tabGeneral": "General", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Host Metrics", - "tabTelnet": "Telnet", "tabSharing": "Sharing", "tabAuthentication": "Authentication", "terminal": "Terminal", @@ -829,8 +825,6 @@ "deselectAll": "Deselect All", "protocols": "Protocols", "secureShell": "Secure Shell", - "virtualNetwork": "Virtual Network", - "unencryptedShell": "Unencrypted shell", "addressIp": "Address / IP", "friendlyName": "Friendly Name", "macAddress": "MAC Address", @@ -853,7 +847,6 @@ "useSocks5ProxyDesc": "Route connection through a proxy server", "connectionOrigin": "Connection Origin", "connectionOriginDesc": "Where this host's connection originates from. Overrides the desktop app's global default.", - "connectionOriginRemoteDesktopNote": "RDP, VNC and Telnet use the remote server unless you pick \"This device\", which needs a guacd this machine can reach — set one in the guacd URL setting, or per host under guacd Proxy.", "connectionOriginDefault": "Use default", "connectionOriginLocal": "This device (local network)", "connectionOriginRemote": "Remote server", @@ -1378,9 +1371,6 @@ "filterAuthStepca": "Step CA", "filterProtocolGroup": "Protocol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Features", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "File Manager", @@ -1478,8 +1468,6 @@ "authTypeDirect": "Direct", "authTypeCredential": "Credential", "authTypeNone": "None", - "colorDepth": "Color Depth", - "resizeMethod": "Resize Method", "recordingPath": "Recording Path", "recordingName": "Recording Name", "saveHostFirst": "Save the host first.", @@ -2117,10 +2105,6 @@ "metrics": "Metrics", "sec": "sec", "logLevel": "Log Level", - "enableRemoteDesktop": "Enable Remote Desktop", - "enableRemoteDesktopDesc": "RDP/VNC remote desktop", - "enableRemoteDesktopDocsLink": "View docs", - "guacdUrl": "guacd URL", "oidcDescription": "Configure OpenID Connect for SSO. Fields marked * are required.", "oidcDocsLink": "View docs", "oidcClientId": "Client ID", @@ -2266,9 +2250,6 @@ "monitoringIntervalInvalid": "Invalid interval values", "monitoringSaved": "Monitoring settings saved", "monitoringSaveFailed": "Failed to save monitoring settings", - "remoteDesktopSaved": "Remote Desktop settings saved", - "remoteDesktopSaveFailed": "Failed to save Remote Desktop settings", - "remoteDesktopUpdateFailed": "Failed to update Remote Desktop setting", "logLevelUpdateFailed": "Failed to update log level", "oidcSaved": "OIDC configuration saved", "oidcSaveFailed": "Failed to save OIDC config", @@ -2457,10 +2438,9 @@ "protocolLabel": "Protocol", "domainLabel": "Domain", "domainPlaceholder": "optional", - "connectToRdp": "Connect via RDP", - "connectToVnc": "Connect via VNC", "connectToTerminal": "Connect to Terminal", - "connectToFiles": "Connect to Files" + "connectToFiles": "Connect to Files", + "connectWith": "Connect via {{protocol}}" }, "sshTools": { "keyRecordingTitle": "Key Recording", @@ -2667,13 +2647,9 @@ "description": "Set values that every host inherits unless it overrides them.", "manage": "Manage", "dialogDescription": "These apply to hosts that leave a setting unset. Anything set on a host wins.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Remote Desktop", "inherit": "Inherit", "on": "On", "off": "Off", - "resizeDisplayUpdate": "Display Update", - "resizeReconnect": "Reconnect", "clearAll": "Clear All", "saved": "Connection defaults saved", "saveFailed": "Failed to save connection defaults", diff --git a/src/ui/locales/translated/af_ZA.json b/src/ui/locales/translated/af_ZA.json index 76fb04853..e8a10303e 100644 --- a/src/ui/locales/translated/af_ZA.json +++ b/src/ui/locales/translated/af_ZA.json @@ -729,7 +729,6 @@ "connectionDetails": "Verbindingsbesonderhede", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Afstandslessenaar", "port": "Hawe", "username": "Gebruikersnaam", "folder": "Vouer", @@ -756,10 +755,7 @@ "tabGeneral": "Algemeen", "tabSsh": "SSH", "tabTerminal": "Terminaal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Gasheermetrieke", - "tabTelnet": "Telnet", "tabSharing": "Deling", "tabAuthentication": "Verifikasie", "terminal": "Terminaal", @@ -833,8 +829,6 @@ "deselectAll": "Deselekteer alles", "protocols": "Protokolle", "secureShell": "Veilige Dop", - "virtualNetwork": "Virtuele Netwerk", - "unencryptedShell": "Ongeënkripteerde dop", "addressIp": "Adres / IP", "friendlyName": "Vriendelike Naam", "macAddress": "MAC-adres", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Stap CA", "filterProtocolGroup": "Protokol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Kenmerke", "filterFeatureTerminal": "Terminaal", "filterFeatureFileManager": "Lêerbestuurder", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direkte", "authTypeCredential": "Geloofsbrief", "authTypeNone": "Geen", - "colorDepth": "Kleurdiepte", - "resizeMethod": "Vergrootingsmetode", "recordingPath": "Opnamepad", "recordingName": "Opnamenaam", "saveHostFirst": "Stoor eers die gasheer.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Aktiveer Guacamole", "enableGuacamoleDesc": "RDP/VNC-afstandlessenaar", "enableGuacamoleDocsLink": "Bekyk dokumente", - "guacdUrl": "guacd-URL", "tailscaleApiKey": "Tailscale API-sleutel", "tailscaleApiKeyDescription": "Word gebruik vir toestelontdekking in die gasheerredigeerder. Ondersteun Tailscale (tskey-) en Headscale (hskey-) sleutels. Genereer 'n sleutel by tailscale.com/admin/settings/keys of vanaf jou Headscale-instansie.", "tailscaleApiKeyDocsLink": "Bekyk dokumente", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokol", "domainLabel": "Domein", "domainPlaceholder": "opsioneel", - "connectToRdp": "Verbind via RDP", - "connectToVnc": "Verbind via VNC", "connectToTerminal": "Koppel aan terminaal", "connectToFiles": "Koppel aan lêers" }, @@ -2633,13 +2619,9 @@ "description": "Stel waardes wat elke gasheer erf tensy dit hulle oorskryf.", "manage": "Bestuur", "dialogDescription": "Dit geld vir gashere wat 'n instelling ongestel laat. Enigiets wat op 'n gasheer gestel word, wen.", - "tabTerminal": "Terminaal", - "tabRemoteDesktop": "Afstandslessenaar", "inherit": "Erf", "on": "Aan", "off": "Af", - "resizeDisplayUpdate": "Skermopdatering", - "resizeReconnect": "Herkoppel", "clearAll": "Vee alles uit", "saved": "Verbindingsverstekke gestoor", "saveFailed": "Kon nie verbindingsverstekinstellings stoor nie" diff --git a/src/ui/locales/translated/ar_SA.json b/src/ui/locales/translated/ar_SA.json index d297ba494..f25c2ec6f 100644 --- a/src/ui/locales/translated/ar_SA.json +++ b/src/ui/locales/translated/ar_SA.json @@ -729,7 +729,6 @@ "connectionDetails": "تفاصيل الاتصال", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "سطح المكتب البعيد", "port": "المنفذ", "username": "اسم المستخدم", "folder": "المجلد", @@ -756,10 +755,7 @@ "tabGeneral": "عام", "tabSsh": "SSH", "tabTerminal": "الطرفية", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "مقاييس المضيف", - "tabTelnet": "Telnet", "tabSharing": "المشاركة", "tabAuthentication": "المصادقة", "terminal": "الطرفية", @@ -833,8 +829,6 @@ "deselectAll": "إلغاء تحديد الكل", "protocols": "البروتوكولات", "secureShell": "Secure Shell", - "virtualNetwork": "الشبكة الافتراضية", - "unencryptedShell": "shell غير مشفر", "addressIp": "العنوان / IP", "friendlyName": "الاسم المألوف", "macAddress": "عنوان MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "الخطوة ج", "filterProtocolGroup": "البروتوكول", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "الميزات", "filterFeatureTerminal": "الطرفية", "filterFeatureFileManager": "مدير الملفات", @@ -1482,8 +1473,6 @@ "authTypeDirect": "مباشر", "authTypeCredential": "بيانات الاعتماد", "authTypeNone": "لا أحد", - "colorDepth": "عمق اللون", - "resizeMethod": "طريقة تغيير الحجم", "recordingPath": "مسار التسجيل", "recordingName": "اسم التسجيل", "saveHostFirst": "احفظ المضيف أولاً.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "تمكين Guacamole", "enableGuacamoleDesc": "سطح المكتب البعيد RDP/VNC", "enableGuacamoleDocsLink": "عرض المستندات", - "guacdUrl": "رابط guacd", "tailscaleApiKey": "مفتاح Tailscale API", "tailscaleApiKeyDescription": "يُستخدم لاكتشاف الأجهزة في محرر المضيف. يدعم مفاتيح Tailscale (tskey-) وHeadscale (hskey-). أنشئ مفتاحًا من خلال tailscale.com/admin/settings/keys أو من خلال حسابك في Headscale.", "tailscaleApiKeyDocsLink": "عرض المستندات", @@ -2424,8 +2412,6 @@ "protocolLabel": "بروتوكول", "domainLabel": "اِختِصاص", "domainPlaceholder": "خياري", - "connectToRdp": "الاتصال عبر بروتوكول سطح المكتب البعيد (RDP)", - "connectToVnc": "الاتصال عبر VNC", "connectToTerminal": "اتصل بالطرفية", "connectToFiles": "اتصل بالملفات" }, @@ -2633,13 +2619,9 @@ "description": "قم بتعيين القيم التي يرثها كل مضيف ما لم يقم بتجاوزها.", "manage": "يدير", "dialogDescription": "تنطبق هذه الشروط على الأجهزة المضيفة التي تترك إعدادًا غير مُحدد. أي إعداد مُحدد على الجهاز المضيف هو الذي يُعتمد.", - "tabTerminal": "صالة", - "tabRemoteDesktop": "سطح المكتب البعيد", "inherit": "يرث", "on": "على", "off": "عن", - "resizeDisplayUpdate": "تحديث الشاشة", - "resizeReconnect": "إعادة الاتصال", "clearAll": "مسح الكل", "saved": "تم حفظ إعدادات الاتصال الافتراضية", "saveFailed": "فشل حفظ إعدادات الاتصال الافتراضية" diff --git a/src/ui/locales/translated/bg_BG.json b/src/ui/locales/translated/bg_BG.json index 51f4fc0bb..64639e15b 100644 --- a/src/ui/locales/translated/bg_BG.json +++ b/src/ui/locales/translated/bg_BG.json @@ -729,7 +729,6 @@ "connectionDetails": "Детайли за връзката", "ssh": "SSH", "telnet": "Телнет", - "remoteDesktop": "Отдалечен работен плот", "port": "Порт", "username": "Потребителско име", "folder": "Папка", @@ -756,10 +755,7 @@ "tabGeneral": "Общи", "tabSsh": "SSH", "tabTerminal": "Терминал", - "tabRdp": "ПРСР", - "tabVnc": "VNC", "tabStats": "Метрики на хоста", - "tabTelnet": "Телнет", "tabSharing": "Споделяне", "tabAuthentication": "Удостоверяване", "terminal": "Терминал", @@ -833,8 +829,6 @@ "deselectAll": "Премахване на избора от всички", "protocols": "Протоколи", "secureShell": "Защитена обвивка", - "virtualNetwork": "Виртуална мрежа", - "unencryptedShell": "Некриптирана обвивка", "addressIp": "Адрес / IP адрес", "friendlyName": "Приятелско име", "macAddress": "MAC адрес", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Стъпка CA", "filterProtocolGroup": "Протокол", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "ПРСР", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Телнет", "filterFeaturesGroup": "Характеристики", "filterFeatureTerminal": "Терминал", "filterFeatureFileManager": "Файлов мениджър", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Директно", "authTypeCredential": "Пълномощия", "authTypeNone": "Няма", - "colorDepth": "Дълбочина на цвета", - "resizeMethod": "Метод за преоразмеряване", "recordingPath": "Път на запис", "recordingName": "Име на записа", "saveHostFirst": "Първо запазете хоста.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Активиране на гуакамоле", "enableGuacamoleDesc": "RDP/VNC отдалечен работен плот", "enableGuacamoleDocsLink": "Преглед на документи", - "guacdUrl": "URL адрес на GUACD", "tailscaleApiKey": "API ключ на Tailscale", "tailscaleApiKeyDescription": "Използва се за откриване на устройства в редактора на хостове. Поддържа ключове Tailscale (tkey-) и Headscale (hskey-). Генерирайте ключ на tailscale.com/admin/settings/keys или от вашия Headscale екземпляр.", "tailscaleApiKeyDocsLink": "Преглед на документи", @@ -2424,8 +2412,6 @@ "protocolLabel": "Протокол", "domainLabel": "Домейн", "domainPlaceholder": "по избор", - "connectToRdp": "Свързване чрез RDP", - "connectToVnc": "Свързване чрез VNC", "connectToTerminal": "Свързване с терминала", "connectToFiles": "Свързване с файлове" }, @@ -2633,13 +2619,9 @@ "description": "Задайте стойности, които всеки хост наследява, освен ако не ги презапише.", "manage": "Управление", "dialogDescription": "Това се отнася за хостове, които оставят настройка незададена. Всичко, което е зададено на хост, печели.", - "tabTerminal": "Терминал", - "tabRemoteDesktop": "Отдалечен работен плот", "inherit": "Наследяване", "on": "Включено", "off": "Изключено", - "resizeDisplayUpdate": "Актуализация на дисплея", - "resizeReconnect": "Свържете се отново", "clearAll": "Изчисти всички", "saved": "Настройките по подразбиране на връзката са запазени", "saveFailed": "Запазването на настройките по подразбиране за връзка не бе успешно" diff --git a/src/ui/locales/translated/bn_BD.json b/src/ui/locales/translated/bn_BD.json index 5a7fa2cdb..13d733d04 100644 --- a/src/ui/locales/translated/bn_BD.json +++ b/src/ui/locales/translated/bn_BD.json @@ -729,7 +729,6 @@ "connectionDetails": "সংযোগের বিবরণ", "ssh": "এসএসএইচ", "telnet": "টেলনেট", - "remoteDesktop": "রিমোট ডেস্কটপ", "port": "বন্দর", "username": "ব্যবহারকারীর নাম", "folder": "ফোল্ডার", @@ -756,10 +755,7 @@ "tabGeneral": "সাধারণ", "tabSsh": "এসএসএইচ", "tabTerminal": "টার্মিনাল", - "tabRdp": "আরডিপি", - "tabVnc": "ভিএনসি", "tabStats": "হোস্ট মেট্রিক্স", - "tabTelnet": "টেলনেট", "tabSharing": "শেয়ারিং", "tabAuthentication": "প্রমাণীকরণ", "terminal": "টার্মিনাল", @@ -833,8 +829,6 @@ "deselectAll": "সবগুলো নির্বাচন বাতিল করুন", "protocols": "প্রোটোকল", "secureShell": "সুরক্ষিত খোলস", - "virtualNetwork": "ভার্চুয়াল নেটওয়ার্ক", - "unencryptedShell": "এনক্রিপ্টবিহীন শেল", "addressIp": "ঠিকানা / আইপি", "friendlyName": "বন্ধুত্বপূর্ণ নাম", "macAddress": "MAC ঠিকানা", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "স্টেপ সিএ", "filterProtocolGroup": "প্রোটোকল", "filterProtocolSsh": "এসএসএইচ", - "filterProtocolRdp": "আরডিপি", - "filterProtocolVnc": "ভিএনসি", - "filterProtocolTelnet": "টেলনেট", "filterFeaturesGroup": "বৈশিষ্ট্য", "filterFeatureTerminal": "টার্মিনাল", "filterFeatureFileManager": "ফাইল ম্যানেজার", @@ -1482,8 +1473,6 @@ "authTypeDirect": "সরাসরি", "authTypeCredential": "শংসাপত্র", "authTypeNone": "কোনোটিই না", - "colorDepth": "রঙের গভীরতা", - "resizeMethod": "আকার পরিবর্তন পদ্ধতি", "recordingPath": "রেকর্ডিং পথ", "recordingName": "রেকর্ডিং নাম", "saveHostFirst": "প্রথমে হোস্টটি সংরক্ষণ করুন।", @@ -2108,7 +2097,6 @@ "enableGuacamole": "গুয়াকামোল সক্রিয় করুন", "enableGuacamoleDesc": "RDP/VNC রিমোট ডেস্কটপ", "enableGuacamoleDocsLink": "নথি দেখুন", - "guacdUrl": "guacd URL", "tailscaleApiKey": "টেইলস্কেল এপিআই কী", "tailscaleApiKeyDescription": "হোস্ট এডিটরে ডিভাইস শনাক্তকরণের জন্য ব্যবহৃত হয়। এটি টেইলস্কেল (tskey-) এবং হেডস্কেল (hskey-) কী সমর্থন করে। tailscale.com/admin/settings/keys থেকে অথবা আপনার হেডস্কেল ইনস্ট্যান্স থেকে একটি কী তৈরি করুন।", "tailscaleApiKeyDocsLink": "নথি দেখুন", @@ -2424,8 +2412,6 @@ "protocolLabel": "প্রোটোকল", "domainLabel": "ডোমেইন", "domainPlaceholder": "ঐচ্ছিক", - "connectToRdp": "RDP এর মাধ্যমে সংযোগ করুন", - "connectToVnc": "VNC এর মাধ্যমে সংযোগ করুন", "connectToTerminal": "টার্মিনালের সাথে সংযোগ করুন", "connectToFiles": "ফাইলগুলির সাথে সংযোগ করুন" }, @@ -2633,13 +2619,9 @@ "description": "এমন মান নির্ধারণ করুন যা প্রতিটি হোস্ট উত্তরাধিকারসূত্রে পাবে, যদি না সে সেগুলোকে অগ্রাহ্য করে।", "manage": "পরিচালনা করুন", "dialogDescription": "এই নিয়মগুলো সেইসব হোস্টের ক্ষেত্রে প্রযোজ্য যারা কোনো সেটিং সেট করে না। হোস্টে যা কিছু সেট করা থাকে, তা-ই কার্যকর হয়।", - "tabTerminal": "টার্মিনাল", - "tabRemoteDesktop": "রিমোট ডেস্কটপ", "inherit": "উত্তরাধিকার সূত্রে", "on": "চালু", "off": "বন্ধ", - "resizeDisplayUpdate": "ডিসপ্লে আপডেট", - "resizeReconnect": "পুনরায় সংযোগ করুন", "clearAll": "সব পরিষ্কার করুন", "saved": "সংযোগের ডিফল্ট মান সংরক্ষিত আছে", "saveFailed": "সংযোগের ডিফল্ট মান সংরক্ষণ করতে ব্যর্থ হয়েছে" diff --git a/src/ui/locales/translated/ca_ES.json b/src/ui/locales/translated/ca_ES.json index 9489f531a..9a984b4e8 100644 --- a/src/ui/locales/translated/ca_ES.json +++ b/src/ui/locales/translated/ca_ES.json @@ -729,7 +729,6 @@ "connectionDetails": "Detalls de la connexió", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Escriptori remot", "port": "Port", "username": "Nom d'usuari", "folder": "Carpeta", @@ -756,10 +755,7 @@ "tabGeneral": "General", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Mètriques de l'amfitrió", - "tabTelnet": "Telnet", "tabSharing": "Compartir", "tabAuthentication": "Autenticació", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Desselecciona-ho tot", "protocols": "Protocols", "secureShell": "Shell segur", - "virtualNetwork": "Xarxa virtual", - "unencryptedShell": "Shell sense xifrar", "addressIp": "Adreça / IP", "friendlyName": "Nom amigable", "macAddress": "Adreça MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Pas CA", "filterProtocolGroup": "Protocol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Característiques", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Gestor de fitxers", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Directe", "authTypeCredential": "Credencial", "authTypeNone": "Cap", - "colorDepth": "Profunditat de color", - "resizeMethod": "Mètode de redimensionament", "recordingPath": "Ruta d'enregistrament", "recordingName": "Nom de l'enregistrament", "saveHostFirst": "Primer desa l'amfitrió.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Activa el guacamole", "enableGuacamoleDesc": "Escriptori remot RDP/VNC", "enableGuacamoleDocsLink": "Veure documents", - "guacdUrl": "URL de guacd", "tailscaleApiKey": "Clau API de Tailscale", "tailscaleApiKeyDescription": "S'utilitza per a la detecció de dispositius a l'editor de l'amfitrió. Admet les claus Tailscale (tskey-) i Headscale (hskey-). Genereu una clau a tailscale.com/admin/settings/keys o des de la vostra instància de Headscale.", "tailscaleApiKeyDocsLink": "Veure documents", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocol", "domainLabel": "Domini", "domainPlaceholder": "opcional", - "connectToRdp": "Connecta't via RDP", - "connectToVnc": "Connecta't mitjançant VNC", "connectToTerminal": "Connecta't al terminal", "connectToFiles": "Connecta't a fitxers" }, @@ -2633,13 +2619,9 @@ "description": "Defineix els valors que cada amfitrió hereta tret que els sobreescrigui.", "manage": "Gestiona", "dialogDescription": "Això s'aplica als amfitrions que deixen una configuració sense definir. Qualsevol cosa definida en un amfitrió guanya.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Escriptori remot", "inherit": "Heretar", "on": "Activat", "off": "Desactivat", - "resizeDisplayUpdate": "Actualització de la pantalla", - "resizeReconnect": "Reconnecta", "clearAll": "Esborra-ho tot", "saved": "Els valors predeterminats de connexió s'han desat", "saveFailed": "No s'han pogut desar els valors predeterminats de la connexió" diff --git a/src/ui/locales/translated/cs_CZ.json b/src/ui/locales/translated/cs_CZ.json index 9e2401fa9..8d10e2b05 100644 --- a/src/ui/locales/translated/cs_CZ.json +++ b/src/ui/locales/translated/cs_CZ.json @@ -729,7 +729,6 @@ "connectionDetails": "Podrobnosti o připojení", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Vzdálená plocha", "port": "Přístav", "username": "Uživatelské jméno", "folder": "Složka", @@ -756,10 +755,7 @@ "tabGeneral": "Generál", "tabSsh": "SSH", "tabTerminal": "Terminál", - "tabRdp": "PRV", - "tabVnc": "VNC", "tabStats": "Metriky hostitele", - "tabTelnet": "Telnet", "tabSharing": "Sdílení", "tabAuthentication": "Ověřování", "terminal": "Terminál", @@ -833,8 +829,6 @@ "deselectAll": "Zrušit výběr všeho", "protocols": "Protokoly", "secureShell": "Bezpečné prostředí", - "virtualNetwork": "Virtuální síť", - "unencryptedShell": "Nešifrovaný shell", "addressIp": "Adresa / IP adresa", "friendlyName": "Přátelský název", "macAddress": "MAC adresa", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Krok CA", "filterProtocolGroup": "Protokol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "PRV", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funkce", "filterFeatureTerminal": "Terminál", "filterFeatureFileManager": "Správce souborů", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Řídit", "authTypeCredential": "Pověření", "authTypeNone": "Žádný", - "colorDepth": "Barevná hloubka", - "resizeMethod": "Metoda změny velikosti", "recordingPath": "Cesta záznamu", "recordingName": "Název nahrávky", "saveHostFirst": "Nejprve uložte hostitele.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Povolit guacamole", "enableGuacamoleDesc": "Vzdálená plocha RDP/VNC", "enableGuacamoleDocsLink": "Zobrazit dokumenty", - "guacdUrl": "URL GUACD", "tailscaleApiKey": "Klíč API Tailscale", "tailscaleApiKeyDescription": "Používá se pro vyhledávání zařízení v editoru hostitele. Podporuje klíče Tailscale (tkey-) a Headscale (hskey-). Klíč si můžete vygenerovat na adrese tailscale.com/admin/settings/keys nebo z vaší instance Headscale.", "tailscaleApiKeyDocsLink": "Zobrazit dokumenty", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokol", "domainLabel": "Doména", "domainPlaceholder": "volitelný", - "connectToRdp": "Připojení přes RDP", - "connectToVnc": "Připojení přes VNC", "connectToTerminal": "Připojení k terminálu", "connectToFiles": "Připojení k souborům" }, @@ -2633,13 +2619,9 @@ "description": "Nastavte hodnoty, které zdědí každý hostitel, pokud je nepřepíše.", "manage": "Spravovat", "dialogDescription": "Toto platí pro hostitele, kteří nechají nastavení nenastavené. Cokoli, co je na hostiteli nastaveno, vyhrává.", - "tabTerminal": "Terminál", - "tabRemoteDesktop": "Vzdálená plocha", "inherit": "Zdědit", "on": "Na", "off": "Vypnuto", - "resizeDisplayUpdate": "Aktualizace displeje", - "resizeReconnect": "Znovu se připojte", "clearAll": "Vymazat vše", "saved": "Výchozí nastavení připojení uloženo", "saveFailed": "Nepodařilo se uložit výchozí nastavení připojení" diff --git a/src/ui/locales/translated/da_DK.json b/src/ui/locales/translated/da_DK.json index 2311472f6..929eb6adc 100644 --- a/src/ui/locales/translated/da_DK.json +++ b/src/ui/locales/translated/da_DK.json @@ -729,7 +729,6 @@ "connectionDetails": "Forbindelsesdetaljer", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Fjernskrivebord", "port": "Havn", "username": "Brugernavn", "folder": "Folder", @@ -756,10 +755,7 @@ "tabGeneral": "Generel", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "Landdistriktsudviklingsplan", - "tabVnc": "VNC", "tabStats": "Værtsmålinger", - "tabTelnet": "Telnet", "tabSharing": "Deling", "tabAuthentication": "Godkendelse", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Fravælg alle", "protocols": "Protokoller", "secureShell": "Sikker skal", - "virtualNetwork": "Virtuelt netværk", - "unencryptedShell": "Ukrypteret skal", "addressIp": "Adresse / IP", "friendlyName": "Venligt navn", "macAddress": "MAC-adresse", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Trin CA", "filterProtocolGroup": "Protokol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "Landdistriktsudviklingsplan", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funktioner", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Filhåndtering", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direkte", "authTypeCredential": "Legitimationsoplysninger", "authTypeNone": "Ingen", - "colorDepth": "Farvedybde", - "resizeMethod": "Ændre størrelse-metode", "recordingPath": "Optagelsessti", "recordingName": "Optagelsesnavn", "saveHostFirst": "Gem værten først.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Aktivér guacamole", "enableGuacamoleDesc": "RDP/VNC fjernskrivebord", "enableGuacamoleDocsLink": "Se dokumenter", - "guacdUrl": "guacd-URL", "tailscaleApiKey": "Tailscale API-nøgle", "tailscaleApiKeyDescription": "Bruges til enhedsregistrering i værtseditoren. Understøtter Tailscale (tskey-) og Headscale (hskey-) nøgler. Generer en nøgle på tailscale.com/admin/settings/keys eller fra din Headscale-instans.", "tailscaleApiKeyDocsLink": "Se dokumenter", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokol", "domainLabel": "Domæne", "domainPlaceholder": "valgfri", - "connectToRdp": "Opret forbindelse via RDP", - "connectToVnc": "Forbind via VNC", "connectToTerminal": "Opret forbindelse til terminalen", "connectToFiles": "Opret forbindelse til filer" }, @@ -2633,13 +2619,9 @@ "description": "Angiv værdier, som hver vært arver, medmindre den tilsidesætter dem.", "manage": "Styre", "dialogDescription": "Disse gælder for værter, der ikke har angivet en indstilling. Alt, hvad der er angivet på en vært, vinder.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Fjernskrivebord", "inherit": "Arve", "on": "På", "off": "Slukket", - "resizeDisplayUpdate": "Opdatering af display", - "resizeReconnect": "Genopret forbindelse", "clearAll": "Ryd alt", "saved": "Standardindstillinger for forbindelser gemt", "saveFailed": "Kunne ikke gemme standardforbindelsesindstillinger" diff --git a/src/ui/locales/translated/de_DE.json b/src/ui/locales/translated/de_DE.json index 37dc7a794..eadb20ba1 100644 --- a/src/ui/locales/translated/de_DE.json +++ b/src/ui/locales/translated/de_DE.json @@ -729,7 +729,6 @@ "connectionDetails": "Verbindungsdetails", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Remotedesktop", "port": "Port", "username": "Benutzername", "folder": "Ordner", @@ -756,10 +755,7 @@ "tabGeneral": "Allgemein", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Host-Metriken", - "tabTelnet": "Telnet", "tabSharing": "Freigabe", "tabAuthentication": "Authentifizierung", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Auswahl aufheben", "protocols": "Protokolle", "secureShell": "Secure Shell", - "virtualNetwork": "Virtuelles Netzwerk", - "unencryptedShell": "Unverschlüsselte Shell", "addressIp": "Adresse / IP", "friendlyName": "Anzeigename", "macAddress": "MAC-Adresse", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Schritt CA", "filterProtocolGroup": "Protokoll", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funktionen", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Dateimanager", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direkt", "authTypeCredential": "Anmeldeinformation", "authTypeNone": "Keiner", - "colorDepth": "Farbtiefe", - "resizeMethod": "Skalierungsmethode", "recordingPath": "Aufzeichnungspfad", "recordingName": "Aufzeichnungsname", "saveHostFirst": "Host zuerst speichern.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Guacamole aktivieren", "enableGuacamoleDesc": "RDP/VNC-Remotedesktop", "enableGuacamoleDocsLink": "Dokumentation anzeigen", - "guacdUrl": "guacd-URL", "tailscaleApiKey": "Tailscale-API-Schlüssel", "tailscaleApiKeyDescription": "Wird zur Geräteerkennung im Host-Editor verwendet. Unterstützt Tailscale- (tskey-) und Headscale-Schlüssel (hskey-). Generieren Sie einen Schlüssel unter tailscale.com/admin/settings/keys oder in Ihrer Headscale-Instanz.", "tailscaleApiKeyDocsLink": "Dokumentation anzeigen", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokoll", "domainLabel": "Domain", "domainPlaceholder": "optional", - "connectToRdp": "Verbindung über RDP herstellen", - "connectToVnc": "Verbindung über VNC herstellen", "connectToTerminal": "Mit Terminal verbinden", "connectToFiles": "Mit Dateien verbinden" }, @@ -2633,13 +2619,9 @@ "description": "Werte festlegen, die jeder Host erbt, sofern er sie nicht überschreibt.", "manage": "Verwalten", "dialogDescription": "Diese Regelungen gelten für Hosts, die keine Einstellung vorgenommen haben. Alle auf einem Host festgelegten Einstellungen haben Vorrang.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Remote-Desktop", "inherit": "Erben", "on": "An", "off": "Aus", - "resizeDisplayUpdate": "Anzeigeaktualisierung", - "resizeReconnect": "Wiederverbinden", "clearAll": "Alles löschen", "saved": "Standardeinstellungen für die Verbindung gespeichert", "saveFailed": "Die Standardverbindungseinstellungen konnten nicht gespeichert werden." diff --git a/src/ui/locales/translated/el_GR.json b/src/ui/locales/translated/el_GR.json index c9b34af7e..f94a23f12 100644 --- a/src/ui/locales/translated/el_GR.json +++ b/src/ui/locales/translated/el_GR.json @@ -729,7 +729,6 @@ "connectionDetails": "Λεπτομέρειες σύνδεσης", "ssh": "SSH", "telnet": "Τέλνετ", - "remoteDesktop": "Απομακρυσμένη επιφάνεια εργασίας", "port": "Λιμάνι", "username": "Όνομα χρήστη", "folder": "Ντοσιέ", @@ -756,10 +755,7 @@ "tabGeneral": "Γενικός", "tabSsh": "SSH", "tabTerminal": "Τερματικό", - "tabRdp": "ΠΑΑ", - "tabVnc": "VNC", "tabStats": "Μετρήσεις κεντρικού υπολογιστή", - "tabTelnet": "Τέλνετ", "tabSharing": "Μοιρασιά", "tabAuthentication": "Πιστοποίηση", "terminal": "Τερματικό", @@ -833,8 +829,6 @@ "deselectAll": "Αποεπιλογή όλων", "protocols": "Πρωτόκολλα", "secureShell": "Ασφαλές κέλυφος", - "virtualNetwork": "Εικονικό Δίκτυο", - "unencryptedShell": "Μη κρυπτογραφημένο κέλυφος", "addressIp": "Διεύθυνση / IP", "friendlyName": "Φιλικό όνομα", "macAddress": "Διεύθυνση MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Βήμα CA", "filterProtocolGroup": "Πρωτόκολλο", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "ΠΑΑ", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Τέλνετ", "filterFeaturesGroup": "Χαρακτηριστικά", "filterFeatureTerminal": "Τερματικό", "filterFeatureFileManager": "Διαχειριστής αρχείων", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Απευθείας", "authTypeCredential": "Πιστοποιητικό", "authTypeNone": "Κανένας", - "colorDepth": "Βάθος χρώματος", - "resizeMethod": "Μέθοδος αλλαγής μεγέθους", "recordingPath": "Διαδρομή εγγραφής", "recordingName": "Όνομα εγγραφής", "saveHostFirst": "Αποθηκεύστε πρώτα τον οικοδεσπότη.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Ενεργοποίηση Γουακαμόλε", "enableGuacamoleDesc": "Απομακρυσμένη επιφάνεια εργασίας RDP/VNC", "enableGuacamoleDocsLink": "Προβολή εγγράφων", - "guacdUrl": "URL guacd", "tailscaleApiKey": "Κλειδί API κλίμακας ουράς", "tailscaleApiKeyDescription": "Χρησιμοποιείται για την ανακάλυψη συσκευών στον επεξεργαστή κεντρικού υπολογιστή. Υποστηρίζει τα κλειδιά Tailscale (tskey-) και Headscale (hskey-). Δημιουργήστε ένα κλειδί στη διεύθυνση tailscale.com/admin/settings/keys ή από την παρουσία Headscale.", "tailscaleApiKeyDocsLink": "Προβολή εγγράφων", @@ -2424,8 +2412,6 @@ "protocolLabel": "Πρωτόκολλο", "domainLabel": "Πεδίο ορισμού", "domainPlaceholder": "προαιρετικός", - "connectToRdp": "Σύνδεση μέσω RDP", - "connectToVnc": "Σύνδεση μέσω VNC", "connectToTerminal": "Σύνδεση με τερματικό", "connectToFiles": "Σύνδεση με αρχεία" }, @@ -2633,13 +2619,9 @@ "description": "Ορίστε τιμές που κληρονομεί κάθε κεντρικός υπολογιστής, εκτός εάν τις παρακάμπτει.", "manage": "Διαχειρίζομαι", "dialogDescription": "Αυτά ισχύουν για κεντρικούς υπολογιστές που αφήνουν μια ρύθμιση μη καθορισμένη. Οτιδήποτε οριστεί σε έναν κεντρικό υπολογιστή κερδίζει.", - "tabTerminal": "Τερματικό", - "tabRemoteDesktop": "Απομακρυσμένη επιφάνεια εργασίας", "inherit": "Κληρονομώ", "on": "Επί", "off": "Μακριά από", - "resizeDisplayUpdate": "Ενημέρωση οθόνης", - "resizeReconnect": "Επανασύνδεση", "clearAll": "Εκκαθάριση όλων", "saved": "Οι προεπιλογές σύνδεσης αποθηκεύτηκαν", "saveFailed": "Η αποθήκευση των προεπιλογών σύνδεσης απέτυχε" diff --git a/src/ui/locales/translated/es_ES.json b/src/ui/locales/translated/es_ES.json index fb261abbd..ca2364167 100644 --- a/src/ui/locales/translated/es_ES.json +++ b/src/ui/locales/translated/es_ES.json @@ -729,7 +729,6 @@ "connectionDetails": "Detalles de la conexión", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Escritorio remoto", "port": "Puerto", "username": "Nombre de usuario", "folder": "Carpeta", @@ -756,10 +755,7 @@ "tabGeneral": "General", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Métricas del host", - "tabTelnet": "Telnet", "tabSharing": "Compartir", "tabAuthentication": "Autenticación", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Deseleccionar todo", "protocols": "Protocolos", "secureShell": "Shell seguro", - "virtualNetwork": "Red virtual", - "unencryptedShell": "Shell sin cifrar", "addressIp": "Dirección / IP", "friendlyName": "Nombre descriptivo", "macAddress": "Dirección MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Paso CA", "filterProtocolGroup": "Protocolo", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Características", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Gestor de archivos", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Directa", "authTypeCredential": "Credencial", "authTypeNone": "Ninguno", - "colorDepth": "Profundidad de color", - "resizeMethod": "Método de redimensionamiento", "recordingPath": "Ruta de grabación", "recordingName": "Nombre de la grabación", "saveHostFirst": "Primero guarde el host.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Activar Guacamole", "enableGuacamoleDesc": "Escritorio remoto RDP/VNC", "enableGuacamoleDocsLink": "Ver documentación", - "guacdUrl": "URL de guacd", "tailscaleApiKey": "Clave de API de Tailscale", "tailscaleApiKeyDescription": "Se utiliza para la detección de dispositivos en el editor del host. Admite claves de Tailscale (tskey-) y Headscale (hskey-). Genere una clave en tailscale.com/admin/settings/keys o desde su instancia de Headscale.", "tailscaleApiKeyDocsLink": "Ver documentación", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocolo", "domainLabel": "Dominio", "domainPlaceholder": "opcional", - "connectToRdp": "Conéctese mediante RDP.", - "connectToVnc": "Conéctese a través de VNC.", "connectToTerminal": "Conectar a Terminal", "connectToFiles": "Conectar a Archivos" }, @@ -2633,13 +2619,9 @@ "description": "Establece valores que heredará cada host a menos que los sobrescriba.", "manage": "Administrar", "dialogDescription": "Esto se aplica a los hosts que dejan una configuración sin establecer. Cualquier configuración establecida en un host prevalecerá.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Escritorio remoto", "inherit": "Heredar", "on": "En", "off": "Apagado", - "resizeDisplayUpdate": "Actualización de pantalla", - "resizeReconnect": "Reconectar", "clearAll": "Borrar todo", "saved": "Se guardaron los valores predeterminados de conexión.", "saveFailed": "No se pudieron guardar los valores predeterminados de conexión." diff --git a/src/ui/locales/translated/fi_FI.json b/src/ui/locales/translated/fi_FI.json index 66f9c0a98..7d9b3f41d 100644 --- a/src/ui/locales/translated/fi_FI.json +++ b/src/ui/locales/translated/fi_FI.json @@ -729,7 +729,6 @@ "connectionDetails": "Yhteystiedot", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Etätyöpöytä", "port": "Portti", "username": "Käyttäjätunnus", "folder": "Kansio", @@ -756,10 +755,7 @@ "tabGeneral": "Yleistä", "tabSsh": "SSH", "tabTerminal": "Terminaali", - "tabRdp": "Maaseudun kehittämisohjelma", - "tabVnc": "VNC", "tabStats": "Isännän mittarit", - "tabTelnet": "Telnet", "tabSharing": "Jakaminen", "tabAuthentication": "Todennus", "terminal": "Terminaali", @@ -833,8 +829,6 @@ "deselectAll": "Poista kaikkien valinta", "protocols": "Protokollat", "secureShell": "Suojattu kuori", - "virtualNetwork": "Virtuaaliverkko", - "unencryptedShell": "Salaamaton kuori", "addressIp": "Osoite / IP-osoite", "friendlyName": "Ystävällinen nimi", "macAddress": "MAC-osoite", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Vaihe CA", "filterProtocolGroup": "Protokolla", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "Maaseudun kehittämisohjelma", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Ominaisuudet", "filterFeatureTerminal": "Terminaali", "filterFeatureFileManager": "Tiedostonhallinta", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Suoraan", "authTypeCredential": "Valtakirja", "authTypeNone": "Ei mitään", - "colorDepth": "Värisyvyys", - "resizeMethod": "Koon muuttamismenetelmä", "recordingPath": "Tallennuspolku", "recordingName": "Tallenteen nimi", "saveHostFirst": "Tallenna ensin isäntä.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Ota guacamole käyttöön", "enableGuacamoleDesc": "RDP/VNC-etätyöpöytä", "enableGuacamoleDocsLink": "Näytä dokumentit", - "guacdUrl": "guacd-URL-osoite", "tailscaleApiKey": "Tailscale API-avain", "tailscaleApiKeyDescription": "Käytetään laitteiden etsimiseen isäntäeditorissa. Tukee Tailscale (tskey-) ja Headscale (hskey-) -avaimia. Luo avain osoitteessa tailscale.com/admin/settings/keys tai Headscale-instanssistasi.", "tailscaleApiKeyDocsLink": "Näytä dokumentit", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokolla", "domainLabel": "Verkkotunnus", "domainPlaceholder": "valinnainen", - "connectToRdp": "Yhdistä RDP:n kautta", - "connectToVnc": "Yhdistä VNC:n kautta", "connectToTerminal": "Yhdistä terminaaliin", "connectToFiles": "Yhdistä tiedostoihin" }, @@ -2633,13 +2619,9 @@ "description": "Aseta arvot, jotka jokainen isäntä perii, ellei se korvaa niitä.", "manage": "Hallitse", "dialogDescription": "Nämä koskevat isäntiä, jotka jättävät asetuksen asettamatta. Mikä tahansa isännälle asetettu arvo voittaa.", - "tabTerminal": "Terminaali", - "tabRemoteDesktop": "Etätyöpöytä", "inherit": "Periä", "on": "Päällä", "off": "Pois", - "resizeDisplayUpdate": "Näyttöpäivitys", - "resizeReconnect": "Yhdistä uudelleen", "clearAll": "Tyhjennä kaikki", "saved": "Yhteysoletukset tallennettu", "saveFailed": "Yhteysasetusten tallentaminen epäonnistui" diff --git a/src/ui/locales/translated/fr_FR.json b/src/ui/locales/translated/fr_FR.json index 0ed88d4b3..983d29379 100644 --- a/src/ui/locales/translated/fr_FR.json +++ b/src/ui/locales/translated/fr_FR.json @@ -729,7 +729,6 @@ "connectionDetails": "Détails de la connexion", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Bureau à distance", "port": "Port", "username": "Nom d'utilisateur", "folder": "Dossier", @@ -756,10 +755,7 @@ "tabGeneral": "Général", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Métriques de l'hôte", - "tabTelnet": "Telnet", "tabSharing": "Partage", "tabAuthentication": "Authentification", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Tout désélectionner", "protocols": "Protocoles", "secureShell": "Secure Shell", - "virtualNetwork": "Réseau virtuel", - "unencryptedShell": "Shell non chiffré", "addressIp": "Adresse / IP", "friendlyName": "Nom convivial", "macAddress": "Adresse MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Étape CA", "filterProtocolGroup": "Protocole", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Fonctionnalités", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Gestionnaire de fichiers", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direct", "authTypeCredential": "Identifiant", "authTypeNone": "Aucun", - "colorDepth": "Profondeur de couleur", - "resizeMethod": "Méthode de redimensionnement", "recordingPath": "Chemin d'enregistrement", "recordingName": "Nom de l'enregistrement", "saveHostFirst": "Enregistrez d'abord l'hôte.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Activer Guacamole", "enableGuacamoleDesc": "Bureau à distance RDP/VNC", "enableGuacamoleDocsLink": "Voir la documentation", - "guacdUrl": "URL guacd", "tailscaleApiKey": "Clé API Tailscale", "tailscaleApiKeyDescription": "Utilisé pour la détection des périphériques dans l'éditeur d'hôtes. Compatible avec les clés Tailscale (tskey-) et Headscale (hskey-). Générez une clé sur tailscale.com/admin/settings/keys ou depuis votre instance Headscale.", "tailscaleApiKeyDocsLink": "Voir la documentation", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocole", "domainLabel": "Domaine", "domainPlaceholder": "facultatif", - "connectToRdp": "Se connecter via RDP", - "connectToVnc": "Connexion via VNC", "connectToTerminal": "Se connecter au terminal", "connectToFiles": "Se connecter aux fichiers" }, @@ -2633,13 +2619,9 @@ "description": "Définissez des valeurs que chaque hôte hérite, sauf s'il les remplace.", "manage": "Gérer", "dialogDescription": "Ces règles s'appliquent aux hôtes qui n'ont pas défini ce paramètre. Tout paramètre défini sur l'hôte prévaut.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Bureau à distance", "inherit": "Hériter", "on": "Sur", "off": "Désactivé", - "resizeDisplayUpdate": "Mise à jour de l'affichage", - "resizeReconnect": "Reconnect", "clearAll": "Effacer tout", "saved": "Paramètres de connexion par défaut enregistrés", "saveFailed": "Impossible d'enregistrer les paramètres de connexion par défaut." diff --git a/src/ui/locales/translated/he_IL.json b/src/ui/locales/translated/he_IL.json index 36e4e78a4..08cfa3ba9 100644 --- a/src/ui/locales/translated/he_IL.json +++ b/src/ui/locales/translated/he_IL.json @@ -729,7 +729,6 @@ "connectionDetails": "פרטי חיבור", "ssh": "SSH", "telnet": "טלנט", - "remoteDesktop": "שולחן עבודה מרוחק", "port": "נָמָל", "username": "שם משתמש", "folder": "תיקייה", @@ -756,10 +755,7 @@ "tabGeneral": "כְּלָלִי", "tabSsh": "SSH", "tabTerminal": "מָסוֹף", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "מדדי מארח", - "tabTelnet": "טלנט", "tabSharing": "שיתוף", "tabAuthentication": "אימות", "terminal": "מָסוֹף", @@ -833,8 +829,6 @@ "deselectAll": "בטל את הבחירה של הכל", "protocols": "פרוטוקולים", "secureShell": "מעטפת מאובטחת", - "virtualNetwork": "רשת וירטואלית", - "unencryptedShell": "מעטפת לא מוצפנת", "addressIp": "כתובת / IP", "friendlyName": "שם ידידותי", "macAddress": "כתובת MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "שלב CA", "filterProtocolGroup": "פּרוֹטוֹקוֹל", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "טלנט", "filterFeaturesGroup": "תכונות", "filterFeatureTerminal": "מָסוֹף", "filterFeatureFileManager": "מנהל הקבצים", @@ -1482,8 +1473,6 @@ "authTypeDirect": "יָשִׁיר", "authTypeCredential": "תְעוּדָה", "authTypeNone": "אַף לֹא אֶחָד", - "colorDepth": "עומק צבע", - "resizeMethod": "שיטת שינוי גודל", "recordingPath": "נתיב ההקלטה", "recordingName": "שם ההקלטה", "saveHostFirst": "שמור את המארח קודם.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "הפעל גוואקמולי", "enableGuacamoleDesc": "שולחן עבודה מרוחק RDP/VNC", "enableGuacamoleDocsLink": "הצג מסמכים", - "guacdUrl": "כתובת אתר של guacd", "tailscaleApiKey": "מפתח ה-API של Tailscale", "tailscaleApiKeyDescription": "משמש לגילוי מכשירים בעורך המארח. תומך במפתחות Tailscale (tskey-) ו-Headscale (hskey-). צור מפתח בכתובת tailscale.com/admin/settings/keys או ממופע Headscale שלך.", "tailscaleApiKeyDocsLink": "הצג מסמכים", @@ -2424,8 +2412,6 @@ "protocolLabel": "פּרוֹטוֹקוֹל", "domainLabel": "תְחוּם", "domainPlaceholder": "אופציונלי", - "connectToRdp": "התחברות דרך RDP", - "connectToVnc": "התחברות דרך VNC", "connectToTerminal": "התחברות לטרמינל", "connectToFiles": "התחברות לקבצים" }, @@ -2633,13 +2619,9 @@ "description": "הגדר ערכים שכל מארח יורש אלא אם כן הוא עוקף אותם.", "manage": "לְנַהֵל", "dialogDescription": "אלה חלים על מארחים שמשאירים הגדרה לא מוגדרת. כל דבר שמוגדר על מארח מנצח.", - "tabTerminal": "מָסוֹף", - "tabRemoteDesktop": "שולחן עבודה מרוחק", "inherit": "לָרֶשֶׁת", "on": "עַל", "off": "כבוי", - "resizeDisplayUpdate": "עדכון תצוגה", - "resizeReconnect": "התחבר מחדש", "clearAll": "נקה הכל", "saved": "ברירות מחדל של החיבור נשמרו", "saveFailed": "נכשלה שמירת ברירות המחדל של החיבור" diff --git a/src/ui/locales/translated/hi_IN.json b/src/ui/locales/translated/hi_IN.json index 8ea9c48ed..0dae8523f 100644 --- a/src/ui/locales/translated/hi_IN.json +++ b/src/ui/locales/translated/hi_IN.json @@ -729,7 +729,6 @@ "connectionDetails": "कनेक्शन विवरण", "ssh": "SSH", "telnet": "टेलनेट", - "remoteDesktop": "दूरस्थ डेस्कटॉप", "port": "पोर्ट", "username": "उपयोगकर्ता नाम", "folder": "फ़ोल्डर", @@ -756,10 +755,7 @@ "tabGeneral": "सामान्य", "tabSsh": "SSH", "tabTerminal": "टर्मिनल", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "होस्ट मेट्रिक्स", - "tabTelnet": "Telnet", "tabSharing": "साझाकरण", "tabAuthentication": "प्रमाणीकरण", "terminal": "टर्मिनल", @@ -833,8 +829,6 @@ "deselectAll": "सभी अचयनित करें", "protocols": "प्रोटोकॉल", "secureShell": "सुरक्षित शेल", - "virtualNetwork": "वर्चुअल नेटवर्क", - "unencryptedShell": "अनएन्क्रिप्टेड शेल", "addressIp": "पता / IP", "friendlyName": "अनुकूल नाम", "macAddress": "MAC पता", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "स्टेप सीए", "filterProtocolGroup": "प्रोटोकॉल", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "विशेषताएं", "filterFeatureTerminal": "टर्मिनल", "filterFeatureFileManager": "फ़ाइल प्रबंधक", @@ -1482,8 +1473,6 @@ "authTypeDirect": "सीधा", "authTypeCredential": "क्रेडेंशियल", "authTypeNone": "कोई नहीं", - "colorDepth": "रंग गहराई", - "resizeMethod": "रीसाइज़ विधि", "recordingPath": "रिकॉर्डिंग पथ", "recordingName": "रिकॉर्डिंग नाम", "saveHostFirst": "पहले होस्ट सहेजें।", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Guacamole सक्षम करें", "enableGuacamoleDesc": "RDP/VNC रिमोट डेस्कटॉप", "enableGuacamoleDocsLink": "दस्तावेज़ देखें", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale API कुंजी", "tailscaleApiKeyDescription": "होस्ट एडिटर में डिवाइस की पहचान के लिए उपयोग किया जाता है। यह टेलस्केल (tskey-) और हेडस्केल (hskey-) कुंजियों का समर्थन करता है। आप tailscale.com/admin/settings/keys पर जाकर या अपने हेडस्केल इंस्टेंस से कुंजी जनरेट कर सकते हैं।", "tailscaleApiKeyDocsLink": "दस्तावेज़ देखें", @@ -2424,8 +2412,6 @@ "protocolLabel": "शिष्टाचार", "domainLabel": "कार्यक्षेत्र", "domainPlaceholder": "वैकल्पिक", - "connectToRdp": "आरडीपी के माध्यम से कनेक्ट करें", - "connectToVnc": "VNC के माध्यम से कनेक्ट करें", "connectToTerminal": "टर्मिनल से कनेक्ट करें", "connectToFiles": "फ़ाइलों से कनेक्ट करें" }, @@ -2633,13 +2619,9 @@ "description": "ऐसे मान निर्धारित करें जिन्हें प्रत्येक होस्ट विरासत में प्राप्त करेगा, जब तक कि वह उन्हें ओवरराइड न कर दे।", "manage": "प्रबंधित करना", "dialogDescription": "ये नियम उन होस्ट पर लागू होते हैं जो कोई सेटिंग नहीं छोड़ते हैं। होस्ट पर जो भी सेटिंग सेट होती है, वही मान्य होती है।", - "tabTerminal": "टर्मिनल", - "tabRemoteDesktop": "दूरवर्ती डेस्कटॉप", "inherit": "इनहेरिट", "on": "पर", "off": "बंद", - "resizeDisplayUpdate": "डिस्प्ले अपडेट", - "resizeReconnect": "रिकनेक्ट", "clearAll": "सभी साफ करें", "saved": "कनेक्शन डिफ़ॉल्ट सहेजे गए", "saveFailed": "कनेक्शन डिफ़ॉल्ट सहेजने में विफल" diff --git a/src/ui/locales/translated/hu_HU.json b/src/ui/locales/translated/hu_HU.json index a963db489..6a4865d8d 100644 --- a/src/ui/locales/translated/hu_HU.json +++ b/src/ui/locales/translated/hu_HU.json @@ -729,7 +729,6 @@ "connectionDetails": "Kapcsolat részletei", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Távoli asztal", "port": "Kikötő", "username": "Felhasználónév", "folder": "Mappa", @@ -756,10 +755,7 @@ "tabGeneral": "Általános", "tabSsh": "SSH", "tabTerminal": "Terminál", - "tabRdp": "Vidékfejlesztési Program", - "tabVnc": "VNC", "tabStats": "Gazdagép-metrikák", - "tabTelnet": "Telnet", "tabSharing": "Megosztás", "tabAuthentication": "Hitelesítés", "terminal": "Terminál", @@ -833,8 +829,6 @@ "deselectAll": "Összes kijelölés törlése", "protocols": "Protokollok", "secureShell": "Biztonságos shell", - "virtualNetwork": "Virtuális hálózat", - "unencryptedShell": "Titkosítatlan shell", "addressIp": "Cím / IP-cím", "friendlyName": "Barátságos név", "macAddress": "MAC-cím", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "CA lépés", "filterProtocolGroup": "Jegyzőkönyv", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "Vidékfejlesztési Program", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Jellemzők", "filterFeatureTerminal": "Terminál", "filterFeatureFileManager": "Fájlkezelő", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Közvetlen", "authTypeCredential": "Hitelesítő adat", "authTypeNone": "Egyik sem", - "colorDepth": "Színmélység", - "resizeMethod": "Átméretezési módszer", "recordingPath": "Felvételi útvonal", "recordingName": "Felvétel neve", "saveHostFirst": "Először mentsd el a gazdagépet.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Guacamole engedélyezése", "enableGuacamoleDesc": "RDP/VNC távoli asztal", "enableGuacamoleDocsLink": "Dokumentumok megtekintése", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale API-kulcs", "tailscaleApiKeyDescription": "Eszközfelderítésre használatos a gazdagép-szerkesztőben. Támogatja a Tailscale (tskey-) és Headscale (hskey-) kulcsokat. Kulcsot generálhat a tailscale.com/admin/settings/keys oldalon vagy a Headscale-példányából.", "tailscaleApiKeyDocsLink": "Dokumentumok megtekintése", @@ -2424,8 +2412,6 @@ "protocolLabel": "Jegyzőkönyv", "domainLabel": "Domain", "domainPlaceholder": "választható", - "connectToRdp": "Csatlakozás RDP-n keresztül", - "connectToVnc": "Csatlakozás VNC-n keresztül", "connectToTerminal": "Csatlakozás a terminálhoz", "connectToFiles": "Kapcsolódás fájlokhoz" }, @@ -2633,13 +2619,9 @@ "description": "Olyan értékeket állítson be, amelyeket minden host örököl, kivéve, ha felülírja őket.", "manage": "Kezelés", "dialogDescription": "Ezek azokra a hosztokra vonatkoznak, amelyek üresen hagynak egy beállítást. Bármi, ami be van állítva egy hoszton, nyer.", - "tabTerminal": "Terminál", - "tabRemoteDesktop": "Távoli asztal", "inherit": "Örököl", "on": "On", "off": "Le", - "resizeDisplayUpdate": "Kijelzőfrissítés", - "resizeReconnect": "Újracsatlakozás", "clearAll": "Összes törlése", "saved": "A kapcsolat alapértelmezett beállításai mentve", "saveFailed": "Nem sikerült menteni a kapcsolat alapértelmezett beállításait" diff --git a/src/ui/locales/translated/id_ID.json b/src/ui/locales/translated/id_ID.json index 446ba389a..36cb1e9fc 100644 --- a/src/ui/locales/translated/id_ID.json +++ b/src/ui/locales/translated/id_ID.json @@ -729,7 +729,6 @@ "connectionDetails": "Detail Koneksi", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Desktop Jarak Jauh", "port": "Pelabuhan", "username": "Nama belakang", "folder": "Map", @@ -756,10 +755,7 @@ "tabGeneral": "Umum", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Metrik Host", - "tabTelnet": "Telnet", "tabSharing": "Membagikan", "tabAuthentication": "Autentikasi", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Batalkan Pilihan Semua", "protocols": "Protokol", "secureShell": "Secure Shell", - "virtualNetwork": "Jaringan Virtual", - "unencryptedShell": "Shell yang tidak terenkripsi", "addressIp": "Alamat / IP", "friendlyName": "Nama yang Ramah", "macAddress": "Alamat MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Langkah CA", "filterProtocolGroup": "Protokol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Fitur", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Pengelola File", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Langsung", "authTypeCredential": "Mandat", "authTypeNone": "Tidak ada", - "colorDepth": "Kedalaman Warna", - "resizeMethod": "Metode Pengubahan Ukuran", "recordingPath": "Jalur Perekaman", "recordingName": "Nama Rekaman", "saveHostFirst": "Selamatkan tuan rumah terlebih dahulu.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Aktifkan Guacamole", "enableGuacamoleDesc": "Desktop jarak jauh RDP/VNC", "enableGuacamoleDocsLink": "Lihat dokumen", - "guacdUrl": "URL guacd", "tailscaleApiKey": "Kunci API Tailscale", "tailscaleApiKeyDescription": "Digunakan untuk pendeteksian perangkat di editor host. Mendukung kunci Tailscale (tskey-) dan Headscale (hskey-). Hasilkan kunci di tailscale.com/admin/settings/keys atau dari instance Headscale Anda.", "tailscaleApiKeyDocsLink": "Lihat dokumen", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokol", "domainLabel": "Domain", "domainPlaceholder": "opsional", - "connectToRdp": "Terhubung melalui RDP", - "connectToVnc": "Terhubung melalui VNC", "connectToTerminal": "Hubungkan ke Terminal", "connectToFiles": "Menghubungkan ke File" }, @@ -2633,13 +2619,9 @@ "description": "Tetapkan nilai-nilai yang akan diwarisi oleh setiap host kecuali jika host tersebut menimpanya.", "manage": "Mengelola", "dialogDescription": "Ketentuan ini berlaku untuk host yang tidak mengatur apa pun. Apa pun yang diatur pada host akan berlaku.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Desktop Jarak Jauh", "inherit": "Mewarisi", "on": "Pada", "off": "Mati", - "resizeDisplayUpdate": "Pembaruan Tampilan", - "resizeReconnect": "Terhubung kembali", "clearAll": "Hapus Semua", "saved": "Pengaturan koneksi default tersimpan.", "saveFailed": "Gagal menyimpan pengaturan default koneksi." diff --git a/src/ui/locales/translated/it_IT.json b/src/ui/locales/translated/it_IT.json index 97cafbf14..e5dfae5d2 100644 --- a/src/ui/locales/translated/it_IT.json +++ b/src/ui/locales/translated/it_IT.json @@ -729,7 +729,6 @@ "connectionDetails": "Dettagli connessione", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Desktop remoto", "port": "Porta", "username": "Nome utente", "folder": "Cartella", @@ -756,10 +755,7 @@ "tabGeneral": "Generale", "tabSsh": "SSH", "tabTerminal": "Terminale", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Metriche host", - "tabTelnet": "Telnet", "tabSharing": "Condivisione", "tabAuthentication": "Autenticazione", "terminal": "Terminale", @@ -833,8 +829,6 @@ "deselectAll": "Deseleziona tutto", "protocols": "Protocolli", "secureShell": "Secure Shell", - "virtualNetwork": "Rete virtuale", - "unencryptedShell": "Shell non crittografata", "addressIp": "Indirizzo / IP", "friendlyName": "Nome descrittivo", "macAddress": "Indirizzo MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Passo CA", "filterProtocolGroup": "Protocollo", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funzionalità", "filterFeatureTerminal": "Terminale", "filterFeatureFileManager": "Gestione file", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Diretta", "authTypeCredential": "Credenziale", "authTypeNone": "Nessuno", - "colorDepth": "Profondità colore", - "resizeMethod": "Metodo di ridimensionamento", "recordingPath": "Percorso registrazione", "recordingName": "Nome registrazione", "saveHostFirst": "Salva prima l'host", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Abilita Guacamole", "enableGuacamoleDesc": "Desktop remoto RDP/VNC", "enableGuacamoleDocsLink": "Visualizza documentazione", - "guacdUrl": "URL guacd", "tailscaleApiKey": "Chiave API Tailscale", "tailscaleApiKeyDescription": "Utilizzata per il rilevamento dei dispositivi nell'editor host. Supporta le chiavi Tailscale (tskey-) e Headscale (hskey-). Genera una chiave su tailscale.com/admin/settings/keys o dalla tua istanza Headscale.", "tailscaleApiKeyDocsLink": "Visualizza documentazione", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocollo", "domainLabel": "Dominio", "domainPlaceholder": "opzionale", - "connectToRdp": "Connettiti tramite RDP", - "connectToVnc": "Connessione tramite VNC", "connectToTerminal": "Connetti al terminale", "connectToFiles": "Connetti ai file" }, @@ -2633,13 +2619,9 @@ "description": "Imposta valori che ogni host eredita a meno che non li sovrascriva.", "manage": "Maneggio", "dialogDescription": "Queste impostazioni si applicano agli host che non specificano alcuna impostazione. Qualsiasi impostazione configurata su un host ha la precedenza.", - "tabTerminal": "terminale", - "tabRemoteDesktop": "Desktop remoto", "inherit": "Ereditare", "on": "SU", "off": "Spento", - "resizeDisplayUpdate": "Aggiornamento della visualizzazione", - "resizeReconnect": "Ricollega", "clearAll": "Cancella tutto", "saved": "Impostazioni di connessione salvate", "saveFailed": "Impossibile salvare le impostazioni di connessione predefinite." diff --git a/src/ui/locales/translated/ja_JP.json b/src/ui/locales/translated/ja_JP.json index fa5eb4a36..8cae3f976 100644 --- a/src/ui/locales/translated/ja_JP.json +++ b/src/ui/locales/translated/ja_JP.json @@ -729,7 +729,6 @@ "connectionDetails": "接続の詳細", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "リモートデスクトップ", "port": "ポート", "username": "ユーザー名", "folder": "フォルダー", @@ -756,10 +755,7 @@ "tabGeneral": "一般", "tabSsh": "SSH", "tabTerminal": "ターミナル", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "ホストメトリクス", - "tabTelnet": "Telnet", "tabSharing": "共有", "tabAuthentication": "認証", "terminal": "ターミナル", @@ -833,8 +829,6 @@ "deselectAll": "すべて選択解除", "protocols": "プロトコル", "secureShell": "セキュアシェル", - "virtualNetwork": "仮想ネットワーク", - "unencryptedShell": "暗号化されていないシェル", "addressIp": "アドレス / IP", "friendlyName": "表示名", "macAddress": "MAC アドレス", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "ステップCA", "filterProtocolGroup": "プロトコル", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "機能", "filterFeatureTerminal": "ターミナル", "filterFeatureFileManager": "ファイルマネージャー", @@ -1482,8 +1473,6 @@ "authTypeDirect": "直接", "authTypeCredential": "認証情報", "authTypeNone": "なし", - "colorDepth": "色深度", - "resizeMethod": "リサイズ方法", "recordingPath": "録画パス", "recordingName": "録画名", "saveHostFirst": "最初にホストを保存してください。", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Guacamoleを有効にする", "enableGuacamoleDesc": "RDP/VNCリモートデスクトップ", "enableGuacamoleDocsLink": "ドキュメントを見る", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale APIキー", "tailscaleApiKeyDescription": "ホストエディタでのデバイス検出に使用されます。Tailscale (tskey-) および Headscale (hskey-) キーをサポートしています。キーは tailscale.com/admin/settings/keys または Headscale インスタンスから生成できます。", "tailscaleApiKeyDocsLink": "ドキュメントを見る", @@ -2424,8 +2412,6 @@ "protocolLabel": "プロトコル", "domainLabel": "ドメイン", "domainPlaceholder": "オプション", - "connectToRdp": "RDP経由で接続する", - "connectToVnc": "VN経由で接続", "connectToTerminal": "ターミナルに接続", "connectToFiles": "ファイルに接続" }, @@ -2633,13 +2619,9 @@ "description": "すべてのホストが継承する値を設定します。ただし、ホストが上書きする場合は除きます。", "manage": "管理", "dialogDescription": "これは、設定が未設定のホストに適用されます。ホストで設定されている内容が優先されます。", - "tabTerminal": "ターミナル", - "tabRemoteDesktop": "リモートデスクトップ", "inherit": "継承する", "on": "の上", "off": "オフ", - "resizeDisplayUpdate": "ディスプレイの更新", - "resizeReconnect": "再接続", "clearAll": "すべてクリア", "saved": "接続のデフォルト設定が保存されました", "saveFailed": "接続設定の保存に失敗しました" diff --git a/src/ui/locales/translated/ko_KR.json b/src/ui/locales/translated/ko_KR.json index 3b168e68a..589c076c6 100644 --- a/src/ui/locales/translated/ko_KR.json +++ b/src/ui/locales/translated/ko_KR.json @@ -729,7 +729,6 @@ "connectionDetails": "연결 세부 정보", "ssh": "SSH", "telnet": "텔넷", - "remoteDesktop": "원격 데스크톱", "port": "포트", "username": "사용자 이름", "folder": "폴더", @@ -756,10 +755,7 @@ "tabGeneral": "일반", "tabSsh": "SSH", "tabTerminal": "터미널", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "호스트 메트릭", - "tabTelnet": "텔넷", "tabSharing": "공유", "tabAuthentication": "인증", "terminal": "터미널", @@ -833,8 +829,6 @@ "deselectAll": "전체 선택 해제", "protocols": "프로토콜", "secureShell": "보안 셸", - "virtualNetwork": "가상 네트워크", - "unencryptedShell": "암호화되지 않은 셸", "addressIp": "주소 / IP", "friendlyName": "친숙한 이름", "macAddress": "MAC 주소", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "스텝 CA", "filterProtocolGroup": "프로토콜", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "텔넷", "filterFeaturesGroup": "기능", "filterFeatureTerminal": "터미널", "filterFeatureFileManager": "파일 관리자", @@ -1482,8 +1473,6 @@ "authTypeDirect": "직접", "authTypeCredential": "자격 증명", "authTypeNone": "없음", - "colorDepth": "색 농도", - "resizeMethod": "크기 조정 방법", "recordingPath": "녹화 경로", "recordingName": "녹화 이름", "saveHostFirst": "호스트를 먼저 저장하세요.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Guacamole 활성화", "enableGuacamoleDesc": "RDP/VNC 원격 데스크톱", "enableGuacamoleDocsLink": "문서 보기", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale API 키", "tailscaleApiKeyDescription": "호스트 편집기에서 장치 검색에 사용됩니다. Tailscale(tskey-) 및 Headscale(hskey-) 키를 지원합니다. 키는 tailscale.com/admin/settings/keys 또는 Headscale 인스턴스에서 생성할 수 있습니다.", "tailscaleApiKeyDocsLink": "문서 보기", @@ -2424,8 +2412,6 @@ "protocolLabel": "규약", "domainLabel": "도메인", "domainPlaceholder": "선택 과목", - "connectToRdp": "RDP를 통해 연결", - "connectToVnc": "VNC를 통해 연결", "connectToTerminal": "터미널에 연결", "connectToFiles": "파일에 연결" }, @@ -2633,13 +2619,9 @@ "description": "호스트가 재정의하지 않는 한 모든 호스트가 상속받는 값을 설정합니다.", "manage": "관리하다", "dialogDescription": "이 설정은 호스트에서 설정을 지정하지 않은 경우에 적용됩니다. 호스트에 설정된 값이 우선 적용됩니다.", - "tabTerminal": "단말기", - "tabRemoteDesktop": "원격 데스크톱", "inherit": "상속하다", "on": "~에", "off": "끄다", - "resizeDisplayUpdate": "디스플레이 업데이트", - "resizeReconnect": "다시 연결", "clearAll": "모두 지우기", "saved": "연결 기본 설정이 저장되었습니다.", "saveFailed": "연결 기본 설정을 저장하는 데 실패했습니다." diff --git a/src/ui/locales/translated/nl_NL.json b/src/ui/locales/translated/nl_NL.json index 73c34ebd8..88f33341a 100644 --- a/src/ui/locales/translated/nl_NL.json +++ b/src/ui/locales/translated/nl_NL.json @@ -729,7 +729,6 @@ "connectionDetails": "Verbindingsgegevens", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Extern bureaublad", "port": "Haven", "username": "Gebruikersnaam", "folder": "Map", @@ -756,10 +755,7 @@ "tabGeneral": "Algemeen", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Hoststatistieken", - "tabTelnet": "Telnet", "tabSharing": "Delen", "tabAuthentication": "Authenticatie", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Alles deselecteren", "protocols": "Protocollen", "secureShell": "Secure Shell", - "virtualNetwork": "Virtueel netwerk", - "unencryptedShell": "Niet-versleutelde shell", "addressIp": "Adres / IP-adres", "friendlyName": "Vriendelijke naam", "macAddress": "MAC-adres", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Stap CA", "filterProtocolGroup": "Protocol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Functies", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Bestandsbeheerder", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direct", "authTypeCredential": "Referentie", "authTypeNone": "Geen", - "colorDepth": "Kleurdiepte", - "resizeMethod": "Formaatwijzigingsmethode", "recordingPath": "Opnamepad", "recordingName": "Opnamenaam", "saveHostFirst": "Sla eerst de host op.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Schakel Guacamole in", "enableGuacamoleDesc": "RDP/VNC-extern bureaublad", "enableGuacamoleDocsLink": "Documenten bekijken", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale API-sleutel", "tailscaleApiKeyDescription": "Wordt gebruikt voor apparaatdetectie in de hosteditor. Ondersteunt Tailscale (tskey-) en Headscale (hskey-) sleutels. Genereer een sleutel op tailscale.com/admin/settings/keys of vanuit uw Headscale-instantie.", "tailscaleApiKeyDocsLink": "Documenten bekijken", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocol", "domainLabel": "Domein", "domainPlaceholder": "optioneel", - "connectToRdp": "Verbinden via RDP", - "connectToVnc": "Verbinden via VNC", "connectToTerminal": "Verbinden met de terminal", "connectToFiles": "Verbinding maken met bestanden" }, @@ -2633,13 +2619,9 @@ "description": "Stel waarden in die elke host overneemt, tenzij deze ze overschrijft.", "manage": "Beheren", "dialogDescription": "Dit geldt voor hosts die een instelling niet hebben ingesteld. Alles wat op een host is ingesteld, heeft voorrang.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Extern bureaublad", "inherit": "Erven", "on": "Op", "off": "Uit", - "resizeDisplayUpdate": "Weergave bijwerken", - "resizeReconnect": "Opnieuw verbinding maken", "clearAll": "Alles wissen", "saved": "Verbindingsstandaarden opgeslagen", "saveFailed": "Het opslaan van de verbindingsinstellingen is mislukt." diff --git a/src/ui/locales/translated/no_NO.json b/src/ui/locales/translated/no_NO.json index 19e790252..86195e72a 100644 --- a/src/ui/locales/translated/no_NO.json +++ b/src/ui/locales/translated/no_NO.json @@ -729,7 +729,6 @@ "connectionDetails": "Tilkoblingsdetaljer", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Eksternt skrivebord", "port": "Havn", "username": "Brukernavn", "folder": "Mappe", @@ -756,10 +755,7 @@ "tabGeneral": "General", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Vertsmålinger", - "tabTelnet": "Telnet", "tabSharing": "Deling", "tabAuthentication": "Autentisering", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Fjern merkingen av alle", "protocols": "Protokoller", "secureShell": "Sikkert skall", - "virtualNetwork": "Virtuelt nettverk", - "unencryptedShell": "Ukryptert skall", "addressIp": "Adresse / IP", "friendlyName": "Vennlig navn", "macAddress": "MAC-adresse", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Trinn CA", "filterProtocolGroup": "Protokoll", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funksjoner", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Filbehandler", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direkte", "authTypeCredential": "Legitimasjon", "authTypeNone": "Ingen", - "colorDepth": "Fargedybde", - "resizeMethod": "Endre størrelse-metoden", "recordingPath": "Opptakssti", "recordingName": "Opptaksnavn", "saveHostFirst": "Lagre verten først.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Aktiver guacamole", "enableGuacamoleDesc": "RDP/VNC eksternt skrivebord", "enableGuacamoleDocsLink": "Vis dokumenter", - "guacdUrl": "guacd-URL", "tailscaleApiKey": "Tailscale API-nøkkel", "tailscaleApiKeyDescription": "Brukes for enhetsoppdagelse i vertredigeringsprogrammet. Støtter Tailscale- (tskey-) og Headscale- (hskey-) nøkler. Generer en nøkkel på tailscale.com/admin/settings/keys eller fra Headscale-instansen din.", "tailscaleApiKeyDocsLink": "Vis dokumenter", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokoll", "domainLabel": "Domene", "domainPlaceholder": "valgfri", - "connectToRdp": "Koble til via RDP", - "connectToVnc": "Koble til via VNC", "connectToTerminal": "Koble til terminalen", "connectToFiles": "Koble til filer" }, @@ -2633,13 +2619,9 @@ "description": "Angi verdier som hver vert arver med mindre den overstyrer dem.", "manage": "Administrer", "dialogDescription": "Disse gjelder for verter som ikke angir en innstilling. Alt som er angitt på en vert vinner.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Eksternt skrivebord", "inherit": "Arve", "on": "På", "off": "Av", - "resizeDisplayUpdate": "Oppdatering av skjerm", - "resizeReconnect": "Koble til igjen", "clearAll": "Fjern alt", "saved": "Standardinnstillinger for tilkobling lagret", "saveFailed": "Kunne ikke lagre standardinnstillinger for tilkobling" diff --git a/src/ui/locales/translated/pl_PL.json b/src/ui/locales/translated/pl_PL.json index a33da04fb..04b50d248 100644 --- a/src/ui/locales/translated/pl_PL.json +++ b/src/ui/locales/translated/pl_PL.json @@ -729,7 +729,6 @@ "connectionDetails": "Szczegóły połączenia", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Pulpit zdalny", "port": "Port", "username": "Nazwa użytkownika", "folder": "Folder", @@ -756,10 +755,7 @@ "tabGeneral": "Ogólne", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Metryki hosta", - "tabTelnet": "Telnet", "tabSharing": "Udostępnianie", "tabAuthentication": "Uwierzytelnianie", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Odznacz wszystko", "protocols": "Protokoły", "secureShell": "Bezpieczna powłoka", - "virtualNetwork": "Sieć wirtualna", - "unencryptedShell": "Niezaszyfrowana powłoka", "addressIp": "Adres / IP", "friendlyName": "Przyjazna nazwa", "macAddress": "Adres MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Krok CA", "filterProtocolGroup": "Protokół", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funkcje", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Menedżer plików", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Bezpośredni", "authTypeCredential": "Poświadczenie", "authTypeNone": "Brak", - "colorDepth": "Głębia koloru", - "resizeMethod": "Metoda zmiany rozmiaru", "recordingPath": "Ścieżka nagrywania", "recordingName": "Nazwa nagrania", "saveHostFirst": "Najpierw zapisz hosta.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Włącz Guacamole", "enableGuacamoleDesc": "Zdalny pulpit RDP/VNC", "enableGuacamoleDocsLink": "Zobacz dokumentację", - "guacdUrl": "Adres URL guacd", "tailscaleApiKey": "Klucz API Tailscale", "tailscaleApiKeyDescription": "Służy do wykrywania urządzeń w edytorze hostów. Obsługuje klucze Tailscale (tskey-) i Headscale (hskey-). Wygeneruj klucz na tailscale.com/admin/settings/keys lub w swojej instancji Headscale.", "tailscaleApiKeyDocsLink": "Zobacz dokumentację", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokół", "domainLabel": "Domena", "domainPlaceholder": "fakultatywny", - "connectToRdp": "Połącz przez RDP", - "connectToVnc": "Połącz przez VNC", "connectToTerminal": "Połącz z terminalem", "connectToFiles": "Połącz z plikami" }, @@ -2633,13 +2619,9 @@ "description": "Ustaw wartości, które dziedziczy każdy host, chyba że je nadpisze.", "manage": "Zarządzaj", "dialogDescription": "Dotyczą hostów, które nie mają ustawionej danej opcji. Ustawienie na hoście ma pierwszeństwo.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Pulpit zdalny", "inherit": "Dziedzicz", "on": "Wł.", "off": "Wył.", - "resizeDisplayUpdate": "Aktualizacja ekranu", - "resizeReconnect": "Połącz ponownie", "clearAll": "Wyczyść wszystko", "saved": "Zapisano domyślne ustawienia połączenia", "saveFailed": "Nie udało się zapisać domyślnych ustawień połączenia" diff --git a/src/ui/locales/translated/pt_BR.json b/src/ui/locales/translated/pt_BR.json index 37f12f548..aa55c5f43 100644 --- a/src/ui/locales/translated/pt_BR.json +++ b/src/ui/locales/translated/pt_BR.json @@ -729,7 +729,6 @@ "connectionDetails": "Detalhes da conexão", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Área de Trabalho Remota", "port": "Porta", "username": "Nome de usuário", "folder": "Pasta", @@ -756,10 +755,7 @@ "tabGeneral": "Em geral", "tabSsh": "SSH", "tabTerminal": "terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Métricas do host", - "tabTelnet": "Telnet", "tabSharing": "Compartilhamento", "tabAuthentication": "Autenticação", "terminal": "terminal", @@ -833,8 +829,6 @@ "deselectAll": "Desmarcar tudo", "protocols": "Protocolos", "secureShell": "Secure Shell", - "virtualNetwork": "Rede Virtual", - "unencryptedShell": "Shell não criptografado", "addressIp": "Endereço IP /", "friendlyName": "Nome amigável", "macAddress": "Endereço MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Etapa CA", "filterProtocolGroup": "Protocolo", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Características", "filterFeatureTerminal": "terminal", "filterFeatureFileManager": "Gerenciador de arquivos", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direto", "authTypeCredential": "Credencial", "authTypeNone": "Nenhum", - "colorDepth": "Profundidade de cor", - "resizeMethod": "Método de redimensionamento", "recordingPath": "Caminho de gravação", "recordingName": "Nome da gravação", "saveHostFirst": "Primeiro, salve o host.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Ativar Guacamole", "enableGuacamoleDesc": "Área de trabalho remota RDP/VNC", "enableGuacamoleDocsLink": "Ver documentos", - "guacdUrl": "URL do guacd", "tailscaleApiKey": "Chave da API Tailscale", "tailscaleApiKeyDescription": "Utilizado para descoberta de dispositivos no editor de hosts. Compatível com chaves Tailscale (tskey-) e Headscale (hskey-). Gere uma chave em tailscale.com/admin/settings/keys ou a partir da sua instância Headscale.", "tailscaleApiKeyDocsLink": "Ver documentos", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocolo", "domainLabel": "Domínio", "domainPlaceholder": "opcional", - "connectToRdp": "Conectar via RDP", - "connectToVnc": "Conecte-se via VNC", "connectToTerminal": "Conectar ao terminal", "connectToFiles": "Conectar aos arquivos" }, @@ -2633,13 +2619,9 @@ "description": "Defina valores que todos os hosts herdarão, a menos que os substituam.", "manage": "Gerenciar", "dialogDescription": "Essas configurações se aplicam a hosts que deixam alguma configuração indefinida. Qualquer configuração definida no host prevalece.", - "tabTerminal": "terminal", - "tabRemoteDesktop": "Área de Trabalho Remota", "inherit": "Herdar", "on": "Sobre", "off": "Desligado", - "resizeDisplayUpdate": "Atualização de exibição", - "resizeReconnect": "Reconectar", "clearAll": "Limpar tudo", "saved": "Configurações de conexão salvas", "saveFailed": "Falha ao salvar as configurações padrão de conexão." diff --git a/src/ui/locales/translated/pt_PT.json b/src/ui/locales/translated/pt_PT.json index f9452b3f0..00dbc70a9 100644 --- a/src/ui/locales/translated/pt_PT.json +++ b/src/ui/locales/translated/pt_PT.json @@ -729,7 +729,6 @@ "connectionDetails": "Detalhes da ligação", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Ambiente de trabalho remoto", "port": "Porta", "username": "Nome de utilizador", "folder": "Pasta", @@ -756,10 +755,7 @@ "tabGeneral": "Geral", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Métricas do Host", - "tabTelnet": "Telnet", "tabSharing": "Partilha", "tabAuthentication": "Autenticação", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Desmarcar Todos", "protocols": "Protocolos", "secureShell": "Shell Seguro", - "virtualNetwork": "Rede Virtual", - "unencryptedShell": "Shell não encriptado", "addressIp": "Endereço / IP", "friendlyName": "Nome Amigável", "macAddress": "Endereço MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Etapa CA", "filterProtocolGroup": "Protocolo", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Funcionalidades", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Gestor de Ficheiros", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direta", "authTypeCredential": "Credencial", "authTypeNone": "Nenhum", - "colorDepth": "Profundidade de cor", - "resizeMethod": "Método de redimensionamento", "recordingPath": "Caminho da gravação", "recordingName": "Nome da gravação", "saveHostFirst": "Guarde o host primeiro.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Ativar Guacamole", "enableGuacamoleDesc": "Ambiente de trabalho remoto RDP/VNC", "enableGuacamoleDocsLink": "Ver documentação", - "guacdUrl": "URL do guacd", "tailscaleApiKey": "Chave de API do Tailscale", "tailscaleApiKeyDescription": "Utilizado para a descoberta de dispositivos no editor de hosts. Compatível com as chaves Tailscale (tskey-) e Headscale (hskey-). Gere uma chave em tailscale.com/admin/settings/keys ou a partir da sua instância Headscale.", "tailscaleApiKeyDocsLink": "Ver documentação", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocolo", "domainLabel": "Domínio", "domainPlaceholder": "opcional", - "connectToRdp": "Ligar via RDP", - "connectToVnc": "Ligue-se via VNC", "connectToTerminal": "Ligar ao Terminal", "connectToFiles": "Ligar aos Ficheiros" }, @@ -2633,13 +2619,9 @@ "description": "Defina valores que todos os hosts herdarão, a menos que os substituam.", "manage": "Gerir", "dialogDescription": "Estas definições aplicam-se a hosts que deixam alguma configuração indefinida. Qualquer configuração definida no host prevalece.", - "tabTerminal": "terminal", - "tabRemoteDesktop": "Área de Trabalho Remota", "inherit": "Herdar", "on": "Em", "off": "Desligado", - "resizeDisplayUpdate": "Atualização de visualização", - "resizeReconnect": "Reconectar", "clearAll": "Limpar tudo", "saved": "Configurações de ligação guardadas", "saveFailed": "Falha ao guardar as definições padrão de ligação." diff --git a/src/ui/locales/translated/ro_RO.json b/src/ui/locales/translated/ro_RO.json index 8cbc8e64e..c78548885 100644 --- a/src/ui/locales/translated/ro_RO.json +++ b/src/ui/locales/translated/ro_RO.json @@ -729,7 +729,6 @@ "connectionDetails": "Detalii conexiune", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Desktop la distanță", "port": "Port", "username": "Nume de utilizator", "folder": "Pliant", @@ -756,10 +755,7 @@ "tabGeneral": "General", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Metrici ale gazdei", - "tabTelnet": "Telnet", "tabSharing": "Partajare", "tabAuthentication": "Autentificare", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Deselectați tot", "protocols": "Protocoale", "secureShell": "Shell securizat", - "virtualNetwork": "Rețea virtuală", - "unencryptedShell": "Shell necriptat", "addressIp": "Adresă / IP", "friendlyName": "Nume prietenos", "macAddress": "Adresă MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Pasul CA", "filterProtocolGroup": "Protocol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Caracteristici", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Manager de fișiere", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direct", "authTypeCredential": "Acreditare", "authTypeNone": "Nici unul", - "colorDepth": "Adâncimea culorii", - "resizeMethod": "Metodă de redimensionare", "recordingPath": "Calea de înregistrare", "recordingName": "Nume înregistrare", "saveHostFirst": "Salvează mai întâi gazda.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Activează Guacamole", "enableGuacamoleDesc": "Desktop la distanță RDP/VNC", "enableGuacamoleDocsLink": "Vizualizați documentele", - "guacdUrl": "URL-ul guacd", "tailscaleApiKey": "Cheie API Tailscale", "tailscaleApiKeyDescription": "Folosit pentru descoperirea dispozitivelor în editorul gazdă. Acceptă cheile Tailscale (tskey-) și Headscale (hskey-). Generați o cheie la tailscale.com/admin/settings/keys sau din instanța Headscale.", "tailscaleApiKeyDocsLink": "Vizualizați documentele", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protocol", "domainLabel": "Domeniu", "domainPlaceholder": "opțional", - "connectToRdp": "Conectați-vă prin RDP", - "connectToVnc": "Conectați-vă prin VNC", "connectToTerminal": "Conectați-vă la terminal", "connectToFiles": "Conectare la fișiere" }, @@ -2633,13 +2619,9 @@ "description": "Setați valorile pe care fiecare gazdă le moștenește, cu excepția cazului în care le suprascrie.", "manage": "Gestionează", "dialogDescription": "Acestea se aplică gazdelor care lasă o setare neconfigurată. Orice setare este setată pe o gazdă este câștigătoare.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Desktop la distanță", "inherit": "Moşteni", "on": "Pe", "off": "Dezactivat", - "resizeDisplayUpdate": "Actualizare afișare", - "resizeReconnect": "Reconectați-vă", "clearAll": "Ștergeți tot", "saved": "Setările implicite ale conexiunii au fost salvate", "saveFailed": "Nu s-au putut salva setările implicite ale conexiunii" diff --git a/src/ui/locales/translated/ru_RU.json b/src/ui/locales/translated/ru_RU.json index 46c37b10f..5d5e3968a 100644 --- a/src/ui/locales/translated/ru_RU.json +++ b/src/ui/locales/translated/ru_RU.json @@ -729,7 +729,6 @@ "connectionDetails": "Детали подключения", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Удаленный рабочий стол", "port": "Порт", "username": "Имя пользователя", "folder": "Папка", @@ -756,10 +755,7 @@ "tabGeneral": "Общие", "tabSsh": "SSH", "tabTerminal": "Терминал", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Метрики хоста", - "tabTelnet": "Telnet", "tabSharing": "Общий доступ", "tabAuthentication": "Аутентификация", "terminal": "Терминал", @@ -833,8 +829,6 @@ "deselectAll": "Снять выделение со всех", "protocols": "Протоколы", "secureShell": "SSH", - "virtualNetwork": "Виртуальная сеть", - "unencryptedShell": "Незашифрованная оболочка", "addressIp": "Адрес / IP", "friendlyName": "Понятное имя", "macAddress": "MAC-адрес", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Шаг CA", "filterProtocolGroup": "Протокол", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Функции", "filterFeatureTerminal": "Терминал", "filterFeatureFileManager": "Файловый менеджер", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Прямой", "authTypeCredential": "Учетные данные", "authTypeNone": "Никто", - "colorDepth": "Глубина цвета", - "resizeMethod": "Метод изменения размера", "recordingPath": "Путь записи", "recordingName": "Имя записи", "saveHostFirst": "Сначала сохраните хост.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Включить Guacamole", "enableGuacamoleDesc": "Удаленный рабочий стол RDP/VNC", "enableGuacamoleDocsLink": "Документация", - "guacdUrl": "URL guacd", "tailscaleApiKey": "API-ключ Tailscale", "tailscaleApiKeyDescription": "Используется для обнаружения устройств в редакторе хоста. Поддерживает ключи Tailscale (tskey-) и Headscale (hskey-). Сгенерируйте ключ на сайте tailscale.com/admin/settings/keys или в вашем экземпляре Headscale.", "tailscaleApiKeyDocsLink": "Документация", @@ -2424,8 +2412,6 @@ "protocolLabel": "Протокол", "domainLabel": "Домен", "domainPlaceholder": "необязательный", - "connectToRdp": "Подключение через RDP", - "connectToVnc": "Подключение через VNC", "connectToTerminal": "Подключиться к терминалу", "connectToFiles": "Подключиться к файлам" }, @@ -2633,13 +2619,9 @@ "description": "Установите значения, которые будут наследоваться каждым хостом, если он их не переопределит.", "manage": "Управлять", "dialogDescription": "Эти правила применяются к хостам, которые не задают какой-либо параметр. Любой параметр, заданный на хосте, имеет приоритет.", - "tabTerminal": "Терминал", - "tabRemoteDesktop": "Удаленный рабочий стол", "inherit": "Наследовать", "on": "На", "off": "Выключенный", - "resizeDisplayUpdate": "Обновление дисплея", - "resizeReconnect": "Переподключитесь", "clearAll": "Очистить все", "saved": "Сохранены параметры подключения по умолчанию.", "saveFailed": "Не удалось сохранить параметры подключения по умолчанию." diff --git a/src/ui/locales/translated/sr_SP.json b/src/ui/locales/translated/sr_SP.json index 332dcd23d..76f30c355 100644 --- a/src/ui/locales/translated/sr_SP.json +++ b/src/ui/locales/translated/sr_SP.json @@ -729,7 +729,6 @@ "connectionDetails": "Детаљи везе", "ssh": "SSH", "telnet": "Телнет", - "remoteDesktop": "Удаљена радна површина", "port": "Лука", "username": "Корисничко име", "folder": "Фасцикла", @@ -756,10 +755,7 @@ "tabGeneral": "Опште", "tabSsh": "SSH", "tabTerminal": "Терминал", - "tabRdp": "РДП", - "tabVnc": "VNC", "tabStats": "Метрике хоста", - "tabTelnet": "Телнет", "tabSharing": "Дељење", "tabAuthentication": "Аутентификација", "terminal": "Терминал", @@ -833,8 +829,6 @@ "deselectAll": "Поништи избор свих", "protocols": "Протоколи", "secureShell": "Безбедна шкољка", - "virtualNetwork": "Виртуелна мрежа", - "unencryptedShell": "Нешифрована љуска", "addressIp": "Адреса / ИП", "friendlyName": "Пријатељско име", "macAddress": "MAC адреса", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Корак CA", "filterProtocolGroup": "Протокол", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "РДП", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Телнет", "filterFeaturesGroup": "Карактеристике", "filterFeatureTerminal": "Терминал", "filterFeatureFileManager": "Менаџер датотека", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Директно", "authTypeCredential": "Акредитив", "authTypeNone": "Ниједан", - "colorDepth": "Дубина боје", - "resizeMethod": "Метод промене величине", "recordingPath": "Путања снимања", "recordingName": "Назив снимка", "saveHostFirst": "Прво сачувајте домаћина.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Омогући гуакамоле", "enableGuacamoleDesc": "RDP/VNC удаљена радна површина", "enableGuacamoleDocsLink": "Прикажи документе", - "guacdUrl": "URL адреса guacd-а", "tailscaleApiKey": "API кључ Tailscale-а", "tailscaleApiKeyDescription": "Користи се за откривање уређаја у уређивачу хоста. Подржава кључеве Tailscale (tkey-) и Headscale (hskey-). Генеришите кључ на tailscale.com/admin/settings/keys или из ваше Headscale инстанце.", "tailscaleApiKeyDocsLink": "Прикажи документе", @@ -2424,8 +2412,6 @@ "protocolLabel": "Протокол", "domainLabel": "Домен", "domainPlaceholder": "опционо", - "connectToRdp": "Повежите се путем RDP-а", - "connectToVnc": "Повежите се преко VNC-а", "connectToTerminal": "Повежи се са терминалом", "connectToFiles": "Повежи се са датотекама" }, @@ -2633,13 +2619,9 @@ "description": "Поставите вредности које сваки хост наслеђује, осим ако их не препише.", "manage": "Управљај", "dialogDescription": "Ово се односи на хостове који не подешавају подешавање. Све што је подешено на хосту побеђује.", - "tabTerminal": "Терминал", - "tabRemoteDesktop": "Удаљена радна површина", "inherit": "Наследи", "on": "Укључено", "off": "Искључено", - "resizeDisplayUpdate": "Ажурирање екрана", - "resizeReconnect": "Поново се повежите", "clearAll": "Обриши све", "saved": "Подразумевана подешавања везе су сачувана", "saveFailed": "Чување подразумеваних подешавања везе није успело" diff --git a/src/ui/locales/translated/sv_SE.json b/src/ui/locales/translated/sv_SE.json index d3b53a3a4..b66a96f91 100644 --- a/src/ui/locales/translated/sv_SE.json +++ b/src/ui/locales/translated/sv_SE.json @@ -729,7 +729,6 @@ "connectionDetails": "Anslutningsdetaljer", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Fjärrskrivbord", "port": "Hamn", "username": "Användarnamn", "folder": "Mapp", @@ -756,10 +755,7 @@ "tabGeneral": "Allmän", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "Landsbygdsutveckling", - "tabVnc": "VNC", "tabStats": "Värdstatistik", - "tabTelnet": "Telnet", "tabSharing": "Delning", "tabAuthentication": "Autentisering", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Avmarkera alla", "protocols": "Protokoll", "secureShell": "Säkert skal", - "virtualNetwork": "Virtuellt nätverk", - "unencryptedShell": "Okrypterat skal", "addressIp": "Adress / IP-adress", "friendlyName": "Vänligt namn", "macAddress": "MAC-adress", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Steg CA", "filterProtocolGroup": "Protokoll", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "Landsbygdsutveckling", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Drag", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Filhanteraren", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Direkt", "authTypeCredential": "Referenser", "authTypeNone": "Ingen", - "colorDepth": "Färgdjup", - "resizeMethod": "Ändra storleksmetod", "recordingPath": "Inspelningsväg", "recordingName": "Inspelningsnamn", "saveHostFirst": "Spara värden först.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Aktivera guacamole", "enableGuacamoleDesc": "RDP/VNC-fjärrskrivbord", "enableGuacamoleDocsLink": "Visa dokument", - "guacdUrl": "guacd-URL", "tailscaleApiKey": "Tailscale API-nyckel", "tailscaleApiKeyDescription": "Används för enhetsidentifiering i värdredigeraren. Stöder Tailscale- (tskey-) och Headscale- (hskey-) nycklar. Generera en nyckel på tailscale.com/admin/settings/keys eller från din Headscale-instans.", "tailscaleApiKeyDocsLink": "Visa dokument", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokoll", "domainLabel": "Domän", "domainPlaceholder": "frivillig", - "connectToRdp": "Anslut via RDP", - "connectToVnc": "Anslut via VNC", "connectToTerminal": "Anslut till terminalen", "connectToFiles": "Anslut till filer" }, @@ -2633,13 +2619,9 @@ "description": "Ange värden som varje värd ärver om den inte åsidosätter dem.", "manage": "Hantera", "dialogDescription": "Dessa gäller för värdar som lämnar en inställning oaktiverad. Allt som ställs in på en värd vinner.", - "tabTerminal": "Terminal", - "tabRemoteDesktop": "Fjärrskrivbord", "inherit": "Ärva", "on": "På", "off": "Av", - "resizeDisplayUpdate": "Uppdatering av displayen", - "resizeReconnect": "Återanslut", "clearAll": "Rensa alla", "saved": "Standardinställningar för anslutning sparade", "saveFailed": "Kunde inte spara standardinställningarna för anslutningen" diff --git a/src/ui/locales/translated/th_TH.json b/src/ui/locales/translated/th_TH.json index 1e7ba230c..8350a202b 100644 --- a/src/ui/locales/translated/th_TH.json +++ b/src/ui/locales/translated/th_TH.json @@ -729,7 +729,6 @@ "connectionDetails": "รายละเอียดการเชื่อมต่อ", "ssh": "เอสเอช", "telnet": "เทลเน็ต", - "remoteDesktop": "รีโมทเดสก์ท็อป", "port": "ท่าเรือ", "username": "ชื่อผู้ใช้", "folder": "โฟลเดอร์", @@ -756,10 +755,7 @@ "tabGeneral": "ทั่วไป", "tabSsh": "เอสเอช", "tabTerminal": "เทอร์มินัล", - "tabRdp": "อาร์ดีพี", - "tabVnc": "วีเอ็นซี", "tabStats": "เมตริกโฮสต์", - "tabTelnet": "เทลเน็ต", "tabSharing": "การแบ่งปัน", "tabAuthentication": "การตรวจสอบสิทธิ์", "terminal": "เทอร์มินัล", @@ -833,8 +829,6 @@ "deselectAll": "ยกเลิกการเลือกทั้งหมด", "protocols": "โปรโตคอล", "secureShell": "เปลือกหุ้มที่ปลอดภัย", - "virtualNetwork": "เครือข่ายเสมือน", - "unencryptedShell": "เชลล์ที่ไม่ได้เข้ารหัส", "addressIp": "ที่อยู่ / IP", "friendlyName": "ชื่อที่เป็นมิตร", "macAddress": "ที่อยู่ MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "ขั้นตอน CA", "filterProtocolGroup": "โปรโตคอล", "filterProtocolSsh": "เอสเอช", - "filterProtocolRdp": "อาร์ดีพี", - "filterProtocolVnc": "วีเอ็นซี", - "filterProtocolTelnet": "เทลเน็ต", "filterFeaturesGroup": "คุณสมบัติ", "filterFeatureTerminal": "เทอร์มินัล", "filterFeatureFileManager": "ตัวจัดการไฟล์", @@ -1482,8 +1473,6 @@ "authTypeDirect": "โดยตรง", "authTypeCredential": "ใบรับรอง", "authTypeNone": "ไม่มี", - "colorDepth": "ความลึกของสี", - "resizeMethod": "วิธีการปรับขนาด", "recordingPath": "เส้นทางการบันทึก", "recordingName": "ชื่อการบันทึก", "saveHostFirst": "บันทึกข้อมูลโฮสต์ก่อน", @@ -2108,7 +2097,6 @@ "enableGuacamole": "เปิดใช้งาน Guacamole", "enableGuacamoleDesc": "การเข้าถึงเดสก์ท็อประยะไกลผ่าน RDP/VNC", "enableGuacamoleDocsLink": "ดูเอกสาร", - "guacdUrl": "URL guacd", "tailscaleApiKey": "คีย์ API ของ Tailscale", "tailscaleApiKeyDescription": "ใช้สำหรับการค้นหาอุปกรณ์ในตัวแก้ไขโฮสต์ รองรับคีย์ Tailscale (tskey-) และ Headscale (hskey-) สร้างคีย์ได้ที่ tailscale.com/admin/settings/keys หรือจากอินสแตนซ์ Headscale ของคุณ", "tailscaleApiKeyDocsLink": "ดูเอกสาร", @@ -2424,8 +2412,6 @@ "protocolLabel": "โปรโตคอล", "domainLabel": "โดเมน", "domainPlaceholder": "ไม่จำเป็น", - "connectToRdp": "เชื่อมต่อผ่าน RDP", - "connectToVnc": "เชื่อมต่อผ่าน VNC", "connectToTerminal": "เชื่อมต่อกับเทอร์มินัล", "connectToFiles": "เชื่อมต่อกับไฟล์" }, @@ -2633,13 +2619,9 @@ "description": "กำหนดค่าต่างๆ ที่โฮสต์ทุกตัวจะได้รับสืบทอด เว้นแต่ว่าโฮสต์นั้นจะกำหนดค่าใหม่เอง", "manage": "จัดการ", "dialogDescription": "ข้อกำหนดเหล่านี้ใช้กับโฮสต์ที่ไม่ได้ตั้งค่าใดๆ ไว้ การตั้งค่าใดๆ บนโฮสต์จะมีผลเหนือกว่า", - "tabTerminal": "เทอร์มินัล", - "tabRemoteDesktop": "รีโมทเดสก์ท็อป", "inherit": "สืบทอด", "on": "บน", "off": "ปิด", - "resizeDisplayUpdate": "อัปเดตการแสดงผล", - "resizeReconnect": "เชื่อมต่อใหม่", "clearAll": "ล้างทั้งหมด", "saved": "บันทึกการตั้งค่าการเชื่อมต่อเริ่มต้นแล้ว", "saveFailed": "ไม่สามารถบันทึกการตั้งค่าการเชื่อมต่อเริ่มต้นได้" diff --git a/src/ui/locales/translated/tr_TR.json b/src/ui/locales/translated/tr_TR.json index eee2d405d..8464738d9 100644 --- a/src/ui/locales/translated/tr_TR.json +++ b/src/ui/locales/translated/tr_TR.json @@ -729,7 +729,6 @@ "connectionDetails": "Bağlantı Ayrıntıları", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Uzak Masaüstü", "port": "Bağlantı Noktası", "username": "Kullanıcı Adı", "folder": "Klasör", @@ -756,10 +755,7 @@ "tabGeneral": "Genel", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Sunucu Metrikleri", - "tabTelnet": "Telnet", "tabSharing": "Paylaşım", "tabAuthentication": "Kimlik Doğrulama", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Tüm Seçimi Kaldır", "protocols": "Protokoller", "secureShell": "Secure Shell", - "virtualNetwork": "Sanal Ağ", - "unencryptedShell": "Şifrelenmemiş Shell", "addressIp": "Adres / IP", "friendlyName": "Kolay Ad", "macAddress": "MAC Adresi", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Adım CA", "filterProtocolGroup": "Protokol", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Özellikler", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Dosya Yöneticisi", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Doğrudan", "authTypeCredential": "Kimlik bilgisi", "authTypeNone": "Hiçbiri", - "colorDepth": "Renk Derinliği", - "resizeMethod": "Yeniden Boyutlandırma Yöntemi", "recordingPath": "Kayıt Yolu", "recordingName": "Kayıt Adı", "saveHostFirst": "Önce ana makineyi kaydedin.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Guacamole'yi Etkinleştir", "enableGuacamoleDesc": "RDP/VNC uzak masaüstü", "enableGuacamoleDocsLink": "Belgeleri görüntüle", - "guacdUrl": "guacd URL'si", "tailscaleApiKey": "Tailscale API Anahtarı", "tailscaleApiKeyDescription": "Ana bilgisayar düzenleyicisinde cihaz keşfi için kullanılır. Tailscale (tskey-) ve Headscale (hskey-) anahtarlarını destekler. Anahtarınızı tailscale.com/admin/settings/keys adresinden veya Headscale örneğinizden oluşturabilirsiniz.", "tailscaleApiKeyDocsLink": "Belgeleri görüntüle", @@ -2424,8 +2412,6 @@ "protocolLabel": "Protokol", "domainLabel": "İhtisas", "domainPlaceholder": "isteğe bağlı", - "connectToRdp": "RDP üzerinden bağlanın", - "connectToVnc": "VNC üzerinden bağlantı kurun", "connectToTerminal": "Terminale Bağlan", "connectToFiles": "Dosyalara Bağlan" }, @@ -2633,13 +2619,9 @@ "description": "Sunucunun kendisi tarafından geçersiz kılınmadığı sürece her sunucunun devralacağı değerler belirleyin.", "manage": "Üstesinden gelmek", "dialogDescription": "Bu kurallar, ayarlarını değiştirmeyen sunucular için geçerlidir. Sunucuda yapılan herhangi bir ayar geçerlidir.", - "tabTerminal": "terminal", - "tabRemoteDesktop": "Uzaktan Masaüstü", "inherit": "Miras almak", "on": "Açık", "off": "Kapalı", - "resizeDisplayUpdate": "Ekran Güncellemesi", - "resizeReconnect": "Yeniden bağlan", "clearAll": "Tümünü Temizle", "saved": "Bağlantı varsayılan ayarları kaydedildi.", "saveFailed": "Bağlantı varsayılan ayarları kaydedilemedi." diff --git a/src/ui/locales/translated/uk_UA.json b/src/ui/locales/translated/uk_UA.json index 8c767e003..8541198fe 100644 --- a/src/ui/locales/translated/uk_UA.json +++ b/src/ui/locales/translated/uk_UA.json @@ -729,7 +729,6 @@ "connectionDetails": "Деталі підключення", "ssh": "SSH", "telnet": "Телнет", - "remoteDesktop": "Віддалений робочий стіл", "port": "Порт", "username": "Ім'я користувача", "folder": "Папка", @@ -756,10 +755,7 @@ "tabGeneral": "Загальне", "tabSsh": "SSH", "tabTerminal": "Термінал", - "tabRdp": "ПРП", - "tabVnc": "VNC", "tabStats": "Метрики хоста", - "tabTelnet": "Телнет", "tabSharing": "Спільне використання", "tabAuthentication": "Автентифікація", "terminal": "Термінал", @@ -833,8 +829,6 @@ "deselectAll": "Зняти вибір усіх", "protocols": "Протоколи", "secureShell": "Безпечна оболонка", - "virtualNetwork": "Віртуальна мережа", - "unencryptedShell": "Незашифрована оболонка", "addressIp": "Адреса / IP-адреса", "friendlyName": "Зрозуміле ім'я", "macAddress": "MAC-адреса", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Крок CA", "filterProtocolGroup": "Протокол", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "ПРП", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Телнет", "filterFeaturesGroup": "Особливості", "filterFeatureTerminal": "Термінал", "filterFeatureFileManager": "Файловий менеджер", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Прямий", "authTypeCredential": "Посвідчення", "authTypeNone": "Жоден", - "colorDepth": "Глибина кольору", - "resizeMethod": "Метод зміни розміру", "recordingPath": "Шлях запису", "recordingName": "Назва запису", "saveHostFirst": "Спочатку збережіть хост.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Увімкнути гуакамоле", "enableGuacamoleDesc": "Віддалений робочий стіл RDP/VNC", "enableGuacamoleDocsLink": "Переглянути документи", - "guacdUrl": "URL-адреса GUACD", "tailscaleApiKey": "Ключ API Tailscale", "tailscaleApiKeyDescription": "Використовується для виявлення пристроїв у редакторі хоста. Підтримує ключі Tailscale (tkey-) та Headscale (hskey-). Згенеруйте ключ на tailscale.com/admin/settings/keys або з вашого екземпляра Headscale.", "tailscaleApiKeyDocsLink": "Переглянути документи", @@ -2424,8 +2412,6 @@ "protocolLabel": "Протокол", "domainLabel": "Домен", "domainPlaceholder": "необов'язково", - "connectToRdp": "Підключення через RDP", - "connectToVnc": "Підключення через VNC", "connectToTerminal": "Підключення до терміналу", "connectToFiles": "Підключення до файлів" }, @@ -2633,13 +2619,9 @@ "description": "Встановіть значення, які успадковує кожен хост, якщо він їх не перевизначить.", "manage": "Керувати", "dialogDescription": "Це стосується хостів, які залишають налаштування невизначеними. Будь-яке налаштування, встановлене на хості, виграє.", - "tabTerminal": "Термінал", - "tabRemoteDesktop": "Віддалений робочий стіл", "inherit": "Успадкувати", "on": "Увімкнено", "off": "Вимкнено", - "resizeDisplayUpdate": "Оновлення дисплея", - "resizeReconnect": "Знову підключитися", "clearAll": "Очистити все", "saved": "Налаштування з'єднання за замовчуванням збережено", "saveFailed": "Не вдалося зберегти налаштування з’єднання за замовчуванням" diff --git a/src/ui/locales/translated/vi_VN.json b/src/ui/locales/translated/vi_VN.json index 34f97e8bc..84dfa5a9f 100644 --- a/src/ui/locales/translated/vi_VN.json +++ b/src/ui/locales/translated/vi_VN.json @@ -729,7 +729,6 @@ "connectionDetails": "Chi tiết kết nối", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "Máy tính từ xa", "port": "Cổng", "username": "Tên đăng nhập", "folder": "Thư mục", @@ -756,10 +755,7 @@ "tabGeneral": "Chung", "tabSsh": "SSH", "tabTerminal": "Terminal", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "Số liệu máy chủ", - "tabTelnet": "Telnet", "tabSharing": "Chia sẻ", "tabAuthentication": "Xác thực", "terminal": "Terminal", @@ -833,8 +829,6 @@ "deselectAll": "Bỏ chọn tất cả", "protocols": "Giao thức", "secureShell": "Secure Shell", - "virtualNetwork": "Mạng ảo", - "unencryptedShell": "Shell không mã hóa", "addressIp": "Địa chỉ / IP", "friendlyName": "Tên hiển thị", "macAddress": "Địa chỉ MAC", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "Bước CA", "filterProtocolGroup": "Giao thức", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "Tính năng", "filterFeatureTerminal": "Terminal", "filterFeatureFileManager": "Trình quản lý file", @@ -1482,8 +1473,6 @@ "authTypeDirect": "Trực tiếp", "authTypeCredential": "Thông tin xác thực", "authTypeNone": "Không có", - "colorDepth": "Độ sâu màu", - "resizeMethod": "Phương thức thay đổi kích thước", "recordingPath": "Đường dẫn ghi", "recordingName": "Tên bản ghi", "saveHostFirst": "Lưu máy chủ trước.", @@ -2108,7 +2097,6 @@ "enableGuacamole": "Bật Guacamole", "enableGuacamoleDesc": "Kết nối RDP/VNC từ xa", "enableGuacamoleDocsLink": "Xem tài liệu", - "guacdUrl": "URL guacd", "tailscaleApiKey": "Khóa API Tailscale", "tailscaleApiKeyDescription": "Được sử dụng để phát hiện thiết bị trong trình chỉnh sửa máy chủ. Hỗ trợ khóa Tailscale (tskey-) và Headscale (hskey-). Tạo khóa tại tailscale.com/admin/settings/keys hoặc từ phiên bản Headscale của bạn.", "tailscaleApiKeyDocsLink": "Xem tài liệu", @@ -2424,8 +2412,6 @@ "protocolLabel": "Giao thức", "domainLabel": "Lãnh địa", "domainPlaceholder": "không bắt buộc", - "connectToRdp": "Kết nối qua RDP", - "connectToVnc": "Kết nối qua VNC", "connectToTerminal": "Kết nối đến Terminal", "connectToFiles": "Kết nối đến Files" }, @@ -2633,13 +2619,9 @@ "description": "Đặt các giá trị mà mọi máy chủ đều kế thừa trừ khi nó ghi đè lên các giá trị đó.", "manage": "Quản lý", "dialogDescription": "Các quy định này áp dụng cho các máy chủ không thiết lập bất kỳ cài đặt nào. Bất kỳ cài đặt nào được thiết lập trên máy chủ đều sẽ được ưu tiên.", - "tabTerminal": "Phần cuối", - "tabRemoteDesktop": "Máy tính từ xa", "inherit": "Kế thừa", "on": "TRÊN", "off": "Tắt", - "resizeDisplayUpdate": "Cập nhật hiển thị", - "resizeReconnect": "Kết nối lại", "clearAll": "Xóa tất cả", "saved": "Cài đặt kết nối mặc định đã được lưu", "saveFailed": "Không thể lưu cài đặt kết nối mặc định." diff --git a/src/ui/locales/translated/zh_CN.json b/src/ui/locales/translated/zh_CN.json index c2dfae73e..a27d277c0 100644 --- a/src/ui/locales/translated/zh_CN.json +++ b/src/ui/locales/translated/zh_CN.json @@ -729,7 +729,6 @@ "connectionDetails": "连接详情", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "远程桌面", "port": "端口", "username": "用户名", "folder": "文件夹", @@ -756,10 +755,7 @@ "tabGeneral": "常规", "tabSsh": "SSH", "tabTerminal": "终端", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "主机指标", - "tabTelnet": "Telnet", "tabSharing": "共享", "tabAuthentication": "认证", "terminal": "终端", @@ -833,8 +829,6 @@ "deselectAll": "取消全选", "protocols": "协议", "secureShell": "SSH", - "virtualNetwork": "虚拟网络", - "unencryptedShell": "未加密 Shell", "addressIp": "地址 / IP", "friendlyName": "友好名称", "macAddress": "MAC 地址", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "步骤 CA", "filterProtocolGroup": "协议", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "功能", "filterFeatureTerminal": "终端", "filterFeatureFileManager": "文件管理器", @@ -1482,8 +1473,6 @@ "authTypeDirect": "直接", "authTypeCredential": "凭据", "authTypeNone": "无", - "colorDepth": "颜色深度", - "resizeMethod": "调整大小方法", "recordingPath": "录制路径", "recordingName": "录制名称", "saveHostFirst": "请先保存主机。", @@ -2108,7 +2097,6 @@ "enableGuacamole": "启用 Guacamole", "enableGuacamoleDesc": "RDP/VNC 远程桌面", "enableGuacamoleDocsLink": "查看文档", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale API 密钥", "tailscaleApiKeyDescription": "用于主机编辑器中的设备发现。支持 Tailscale (tskey-) 和 Headscale (hskey-) 密钥。您可以在 tailscale.com/admin/settings/keys 或从您的 Headscale 实例生成密钥。", "tailscaleApiKeyDocsLink": "查看文档", @@ -2424,8 +2412,6 @@ "protocolLabel": "协议", "domainLabel": "领域", "domainPlaceholder": "选修的", - "connectToRdp": "通过 RDP 连接", - "connectToVnc": "通过 VNC 连接", "connectToTerminal": "连接到终端", "connectToFiles": "连接到文件管理器" }, @@ -2633,13 +2619,9 @@ "description": "设置所有主机都将继承的值,除非主机覆盖这些值。", "manage": "管理", "dialogDescription": "这些规则适用于未设置相关设置的主机。主机上已设置的任何内容均会生效。", - "tabTerminal": "终端", - "tabRemoteDesktop": "远程桌面", "inherit": "继承", "on": "开启", "off": "关闭", - "resizeDisplayUpdate": "显示更新", - "resizeReconnect": "重新连接", "clearAll": "全部清除", "saved": "已保存连接默认设置", "saveFailed": "保存连接默认设置失败" diff --git a/src/ui/locales/translated/zh_TW.json b/src/ui/locales/translated/zh_TW.json index a9f07550a..799f42514 100644 --- a/src/ui/locales/translated/zh_TW.json +++ b/src/ui/locales/translated/zh_TW.json @@ -729,7 +729,6 @@ "connectionDetails": "連線詳細資料", "ssh": "SSH", "telnet": "Telnet", - "remoteDesktop": "遠端桌面", "port": "連接埠", "username": "使用者名稱", "folder": "資料夾", @@ -756,10 +755,7 @@ "tabGeneral": "一般", "tabSsh": "SSH", "tabTerminal": "終端機", - "tabRdp": "RDP", - "tabVnc": "VNC", "tabStats": "主機指標", - "tabTelnet": "Telnet", "tabSharing": "分享", "tabAuthentication": "驗證", "terminal": "終端機", @@ -833,8 +829,6 @@ "deselectAll": "取消全選", "protocols": "通訊協定", "secureShell": "安全殼層", - "virtualNetwork": "虛擬網路", - "unencryptedShell": "未加密殼層", "addressIp": "位址 / IP", "friendlyName": "易記名稱", "macAddress": "MAC 位址", @@ -1381,9 +1375,6 @@ "filterAuthStepca": "步驟 CA", "filterProtocolGroup": "通訊協定", "filterProtocolSsh": "SSH", - "filterProtocolRdp": "RDP", - "filterProtocolVnc": "VNC", - "filterProtocolTelnet": "Telnet", "filterFeaturesGroup": "功能", "filterFeatureTerminal": "終端機", "filterFeatureFileManager": "檔案管理員", @@ -1482,8 +1473,6 @@ "authTypeDirect": "直接", "authTypeCredential": "憑證", "authTypeNone": "沒有任何", - "colorDepth": "色彩深度", - "resizeMethod": "調整尺寸方法", "recordingPath": "錄製路徑", "recordingName": "錄製名稱", "saveHostFirst": "請先儲存主機。", @@ -2108,7 +2097,6 @@ "enableGuacamole": "啟用 Guacamole", "enableGuacamoleDesc": "RDP/VNC 遠端桌面", "enableGuacamoleDocsLink": "檢視文件", - "guacdUrl": "guacd URL", "tailscaleApiKey": "Tailscale API 金鑰", "tailscaleApiKeyDescription": "用於主機編輯器中的設備發現。支援 Tailscale (tskey-) 和 Headscale (hskey-) 金鑰。您可以在 tailscale.com/admin/settings/keys 或從您的 Headscale 實例產生金鑰。", "tailscaleApiKeyDocsLink": "檢視文件", @@ -2424,8 +2412,6 @@ "protocolLabel": "協定", "domainLabel": "領域", "domainPlaceholder": "選修的", - "connectToRdp": "透過 RDP 連接", - "connectToVnc": "透過 VNC 連接", "connectToTerminal": "連線至終端機", "connectToFiles": "連線至檔案管理員" }, @@ -2633,13 +2619,9 @@ "description": "設定所有主機都將繼承的值,除非主機覆寫這些值。", "manage": "管理", "dialogDescription": "這些規則適用於未設定相關設定的主機。主機上已設定的任何內容均會生效。", - "tabTerminal": "終端", - "tabRemoteDesktop": "遠端桌面", "inherit": "繼承", "on": "在", "off": "離開", - "resizeDisplayUpdate": "顯示更新", - "resizeReconnect": "重新連接", "clearAll": "全部清除", "saved": "已儲存連線預設設定", "saveFailed": "儲存連線預設設定失敗" diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 95bf93849..ece2d2b67 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -794,7 +794,7 @@ function getApiUrl(path: string, defaultPort: number): string { // JWT fresh on every request (cheap, and correct even if the user // connects/disconnects remote sync without an app reload). -function createRemoteOriginApiInstance(path: string): AxiosInstance { +export function createRemoteOriginApiInstance(path: string): AxiosInstance { const instance = axios.create({ headers: { "Content-Type": "application/json" }, timeout: 30000, @@ -835,7 +835,6 @@ function createRemoteOriginApiInstance(path: string): AxiosInstance { let remoteFileManagerApi: AxiosInstance | null = null; let remoteStatsApi: AxiosInstance | null = null; let remoteCoreApi: AxiosInstance | null = null; -let remoteGuacamoleApi: AxiosInstance | null = null; export function getRemoteFileManagerApi(): AxiosInstance { if (!remoteFileManagerApi) { @@ -867,13 +866,6 @@ export function getRemoteCoreApi(): AxiosInstance { return remoteCoreApi; } -export function getRemoteGuacamoleApi(): AxiosInstance { - if (!remoteGuacamoleApi) { - remoteGuacamoleApi = createRemoteOriginApiInstance(""); - } - return remoteGuacamoleApi; -} - // Maps a live SSH session (keyed by sessionId, which today is the host's // numeric id as a string -- see ensureSSHSessionForHost) to the resolved // origin it was connected through, so every subsequent file-manager call @@ -1582,8 +1574,6 @@ export { updateLogLevel, getSessionTimeout, updateSessionTimeout, - getGuacamoleSettings, - updateGuacamoleSettings, } from "@/api/settings-api"; // ============================================================================ diff --git a/src/ui/plugin-host/app.ts b/src/ui/plugin-host/app.ts index cada5ff88..f7654aed5 100644 --- a/src/ui/plugin-host/app.ts +++ b/src/ui/plugin-host/app.ts @@ -18,6 +18,7 @@ import { registerHostContextMenuItem, } from "@/sidebar/host-contributions"; import { registerPaletteEntry } from "@/shell/palette-registry"; +import { registerHostProtocol } from "@/sidebar/host-protocols"; import { registerDashboardCard, type DashboardCardRenderProps, @@ -30,7 +31,7 @@ import { registerAction, registerSlotContribution, } from "@/shell/action-registry"; -import { pluginWsUrl } from "@/lib/plugin-transport"; +import { pluginApiFor, pluginWsUrl } from "@/lib/plugin-transport"; import { registerPluginComponent } from "./component-registry"; import type { WidgetTypeDefinition } from "@/types/homepage-types"; import type { LucideIcon } from "lucide-react"; @@ -208,6 +209,19 @@ export function createPluginApp( ); }, + registerHostProtocol(protocol) { + return track( + registerHostProtocol({ + ...protocol, + pluginId, + titleKey: key(protocol.titleKey), + descriptionKey: protocol.descriptionKey + ? key(protocol.descriptionKey) + : undefined, + }), + ); + }, + registerHostBadge(badge) { return track( registerHostBadge({ @@ -367,6 +381,12 @@ export function createPluginApp( } }, api: pluginHostBridge.getApi(pluginId), + apiFor: (origin) => + pluginApiFor( + pluginId, + origin as never, + pluginHostBridge.getApi(pluginId) as never, + ) as unknown as TermixApp["api"], wsUrl: (path, options) => pluginWsUrl(pluginId, path, options as never) as ReturnType< TermixApp["wsUrl"] diff --git a/src/ui/plugin-host/bridge.ts b/src/ui/plugin-host/bridge.ts index b132da05c..fda329e6a 100644 --- a/src/ui/plugin-host/bridge.ts +++ b/src/ui/plugin-host/bridge.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from "react"; +import { enabledHostProtocols } from "@/sidebar/host-protocols"; import { useTranslation as useI18nTranslation } from "react-i18next"; import { toast } from "sonner"; import { @@ -275,6 +276,13 @@ export const pluginHostBridge: PluginHostBridge = { useSlotContributions: (slotId, context) => useActionSlot(slotId, context), + hostProtocols: (record) => [ + ...(record.enableSsh !== false ? ["ssh"] : []), + ...enabledHostProtocols( + record as { pluginSettings?: Record> }, + ).map((protocol) => protocol.id), + ], + usePluginComponent: (id) => usePluginComponent(id), }; diff --git a/src/ui/plugin-host/sdk-ui.ts b/src/ui/plugin-host/sdk-ui.ts index 546c4f667..11ecfe54a 100644 --- a/src/ui/plugin-host/sdk-ui.ts +++ b/src/ui/plugin-host/sdk-ui.ts @@ -112,7 +112,16 @@ export { type ConnectionOrigin, } from "@/lib/connection-origin"; export { pluginWsUrl } from "@/lib/plugin-transport"; -export { hydrateLocalSharedHostAuth } from "@/lib/remote-server-api"; +export { + buildOriginWsUrl, + type WebSocketConnectionTarget, +} from "@/lib/connection-origin"; +export { getBasePath } from "@/lib/base-path"; +export type { ConnectionStage } from "@/types/connection-log"; +export { + hydrateLocalSharedHostAuth, + resolveRemoteHostId, +} from "@/lib/remote-server-api"; export { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext"; // Core APIs a terminal-like surface calls: recent activity, the host's stored diff --git a/src/ui/plugin-host/testing-host.tsx b/src/ui/plugin-host/testing-host.tsx index fdd0d5fe9..97b0c24ff 100644 --- a/src/ui/plugin-host/testing-host.tsx +++ b/src/ui/plugin-host/testing-host.tsx @@ -28,6 +28,7 @@ import { import { getPanel, listPanels } from "@/shell/panel-registry"; import { listRegisteredRailItems } from "@/sidebar/rail-items"; import { listHostActions } from "@/sidebar/host-contributions"; +import { listHostProtocols } from "@/sidebar/host-protocols"; import { getHostEditorSection, hostEditorSectionList, @@ -207,6 +208,8 @@ export async function renderPlugin( id: action.id, tabType: action.tabType, })), + hostProtocols: () => + mine(listHostProtocols()).map((protocol) => protocol.id), hostEditorSections: () => mine(hostEditorSectionList()).map((section) => section.id), dashboardCards: () => diff --git a/src/ui/settings/HostPluginSections.tsx b/src/ui/settings/HostPluginSections.tsx index 7cd9fb317..dbce26e37 100644 --- a/src/ui/settings/HostPluginSections.tsx +++ b/src/ui/settings/HostPluginSections.tsx @@ -17,7 +17,7 @@ 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"; +import { hasVisibleFields, isFieldActive } from "./settings-fields-util"; /** Values for every plugin on one host: { [pluginId]: { [key]: value } }. */ export type HostPluginSettings = Record>; @@ -45,7 +45,7 @@ export function usePluginHostSections(): PluginSummary[] { plugins.filter((plugin) => { if (!plugin.enabled) return false; const host = plugin.contributes?.settings?.host; - return !!host && (host.fields.length > 0 || !!host.enableKey); + return !!host && (hasVisibleFields(host.fields) || !!host.enableKey); }), [plugins], ); diff --git a/src/ui/settings/host-plugins-tab.tsx b/src/ui/settings/host-plugins-tab.tsx index 58099d50e..3594ecb02 100644 --- a/src/ui/settings/host-plugins-tab.tsx +++ b/src/ui/settings/host-plugins-tab.tsx @@ -20,6 +20,7 @@ import { HostPluginSections, type HostPluginSettings, } from "./HostPluginSections"; +import { hasVisibleFields } from "./settings-fields-util"; export const HOST_PLUGINS_TAB_ID = "plugins"; @@ -38,7 +39,7 @@ export function syncHostPluginsTab(plugins: PluginSummary[]): boolean { const contributors = plugins.filter((plugin) => { if (!plugin.enabled) return false; const host = plugin.contributes?.settings?.host; - return !!host && (host.fields.length > 0 || !!host.enableKey); + return !!host && (hasVisibleFields(host.fields) || !!host.enableKey); }); if (contributors.length === 0) { diff --git a/src/ui/settings/settings-fields-util.ts b/src/ui/settings/settings-fields-util.ts index b510b486b..dbf527a8f 100644 --- a/src/ui/settings/settings-fields-util.ts +++ b/src/ui/settings/settings-fields-util.ts @@ -1,7 +1,8 @@ import type { PluginSettingsField } from "@/api/plugins-api"; /** - * Whether a field's `requires` gate is satisfied. + * Whether the generic form draws a field: not hidden (the plugin edits it in + * its own UI) and its `requires` gate satisfied. * * Kept out of the component file so both the settings page and the host editor * section can import it without dragging a component along. @@ -10,6 +11,12 @@ export function isFieldActive( field: PluginSettingsField, values: Record, ): boolean { + if (field.hidden) return false; if (!field.requires) return true; return values[field.requires] === true; } + +/** Whether a plugin has any field the generic form would ever draw. */ +export function hasVisibleFields(fields: PluginSettingsField[]): boolean { + return fields.some((field) => !field.hidden); +} diff --git a/src/ui/sidebar/AdminSettingsPanel.tsx b/src/ui/sidebar/AdminSettingsPanel.tsx index a9b8071a8..398a9eb04 100644 --- a/src/ui/sidebar/AdminSettingsPanel.tsx +++ b/src/ui/sidebar/AdminSettingsPanel.tsx @@ -33,8 +33,6 @@ import { updateGlobalMonitoringSettings, getLogLevel, updateLogLevel, - getGuacamoleSettings, - updateGuacamoleSettings, getOidcAutoProvision, updateOidcAutoProvision, getSecondFactorAfterExternalLogin, @@ -137,8 +135,6 @@ export function AdminSettingsPanel({ const [sessionTimeout, setSessionTimeout] = useState("24"); const [statusInterval, setStatusInterval] = useState("60"); const [metricsInterval, setMetricsInterval] = useState("30"); - const [guacEnabled, setGuacEnabled] = useState(false); - const [guacUrl, setGuacUrl] = useState("guacd:4822"); const [logLevel, setLogLevel] = useState("info"); const [analyticsEnabled, setAnalyticsEnabled] = useState(true); const [analyticsLocked, setAnalyticsLocked] = useState(false); @@ -350,7 +346,6 @@ export function AdminSettingsPanel({ timeout, monitoring, level, - guac, oidcProv, secondFactorExternal, oidcSilent, @@ -366,7 +361,6 @@ export function AdminSettingsPanel({ getSessionTimeout(), getGlobalMonitoringSettings(), getLogLevel(), - getGuacamoleSettings(), getOidcAutoProvision(), getSecondFactorAfterExternalLogin(), getOidcSilentLoginDefault(), @@ -402,10 +396,6 @@ export function AdminSettingsPanel({ } if (level.status === "fulfilled") setLogLevel(level.value.level); - if (guac.status === "fulfilled") { - setGuacEnabled(guac.value.enabled); - setGuacUrl(guac.value.url || "guacd:4822"); - } if (analytics.status === "fulfilled") { setAnalyticsEnabled(analytics.value.enabled); setAnalyticsLocked(analytics.value.locked ?? false); @@ -656,26 +646,6 @@ export function AdminSettingsPanel({ } } - async function handleSaveGuacamole() { - try { - await updateGuacamoleSettings({ enabled: guacEnabled, url: guacUrl }); - toast.success(t("admin.remoteDesktopSaved")); - } catch { - toast.error(t("admin.remoteDesktopSaveFailed")); - } - } - - async function handleToggleGuacamole() { - const newVal = !guacEnabled; - setGuacEnabled(newVal); - try { - await updateGuacamoleSettings({ enabled: newVal, url: guacUrl }); - } catch { - setGuacEnabled(!newVal); - toast.error(t("admin.remoteDesktopUpdateFailed")); - } - } - async function handleSaveLogLevel(level: string) { setLogLevel(level); try { @@ -1103,11 +1073,6 @@ export function AdminSettingsPanel({ metricsInterval={metricsInterval} setMetricsInterval={setMetricsInterval} handleSaveMonitoring={handleSaveMonitoring} - guacEnabled={guacEnabled} - handleToggleGuacamole={handleToggleGuacamole} - guacUrl={guacUrl} - setGuacUrl={setGuacUrl} - handleSaveGuacamole={handleSaveGuacamole} logLevel={logLevel} handleSaveLogLevel={handleSaveLogLevel} /> diff --git a/src/ui/sidebar/AdminSettingsSections.tsx b/src/ui/sidebar/AdminSettingsSections.tsx index 060400ca4..935bd078f 100644 --- a/src/ui/sidebar/AdminSettingsSections.tsx +++ b/src/ui/sidebar/AdminSettingsSections.tsx @@ -60,11 +60,6 @@ type GeneralSettingsSectionProps = { metricsInterval: string; setMetricsInterval: Dispatch>; handleSaveMonitoring: () => void; - guacEnabled: boolean; - handleToggleGuacamole: () => void; - guacUrl: string; - setGuacUrl: Dispatch>; - handleSaveGuacamole: () => void; logLevel: string; handleSaveLogLevel: (level: string) => void; }; @@ -106,11 +101,6 @@ export function AdminGeneralSettingsSection({ metricsInterval, setMetricsInterval, handleSaveMonitoring, - guacEnabled, - handleToggleGuacamole, - guacUrl, - setGuacUrl, - handleSaveGuacamole, logLevel, handleSaveLogLevel, }: GeneralSettingsSectionProps) { @@ -390,50 +380,6 @@ export function AdminGeneralSettingsSection({
-
- - {t("admin.enableRemoteDesktopDesc")}{" "} - - {t("admin.enableRemoteDesktopDocsLink")} - - - } - > - - - {guacEnabled && ( -
- -
- setGuacUrl(e.target.value)} - placeholder="guacd:4822" - className="text-sm" - /> - -
-
- )} -
-
{t("admin.logLevel")} diff --git a/src/ui/sidebar/AdminUserManagePanel.tsx b/src/ui/sidebar/AdminUserManagePanel.tsx index 1627924f2..7ca88b812 100644 --- a/src/ui/sidebar/AdminUserManagePanel.tsx +++ b/src/ui/sidebar/AdminUserManagePanel.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { hostProtocolFlags, type HostProtocols } from "./host-protocols"; import { getUserList } from "@/main-axios"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -141,12 +142,9 @@ export function AdminUserManagePanel({ }, [user.id]); const [editor, setEditor] = useState(null); const [editorTab, setEditorTab] = useState("general"); - const [editorProtocols, setEditorProtocols] = useState({ - enableSsh: true, - enableRdp: false, - enableVnc: false, - enableTelnet: false, - }); + const [editorProtocols, setEditorProtocols] = useState(() => + hostProtocolFlags(null), + ); const [confirmDialog, setConfirmDialog] = useState<{ message: string; onConfirm: () => void; @@ -791,12 +789,7 @@ export function AdminUserManagePanel({ size="sm" className="h-6 text-[10px] border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand" onClick={() => { - setEditorProtocols({ - enableSsh: true, - enableRdp: false, - enableVnc: false, - enableTelnet: false, - }); + setEditorProtocols(hostProtocolFlags(null)); setEditorTab("general"); setEditor({ kind: "host", host: null }); }} @@ -842,12 +835,7 @@ export function AdminUserManagePanel({ size="icon" className="size-6 text-muted-foreground hover:text-foreground" onClick={() => { - setEditorProtocols({ - enableSsh: host.enableSsh, - enableRdp: host.enableRdp, - enableVnc: host.enableVnc, - enableTelnet: host.enableTelnet, - }); + setEditorProtocols(hostProtocolFlags(host)); setEditorTab("general"); setEditor({ kind: "host", host }); }} diff --git a/src/ui/sidebar/ConnectionDefaultsSettings.tsx b/src/ui/sidebar/ConnectionDefaultsSettings.tsx index 8093dc1ff..b1ceaa627 100644 --- a/src/ui/sidebar/ConnectionDefaultsSettings.tsx +++ b/src/ui/sidebar/ConnectionDefaultsSettings.tsx @@ -20,7 +20,6 @@ import { import { fromTriState, toTriState, - type RemoteDesktopDefaults, type TerminalDefaults, type TriState, } from "@/lib/connection-defaults"; @@ -35,34 +34,6 @@ const inputClass = const labelClass = "text-[10px] font-bold uppercase tracking-widest text-muted-foreground"; -const RDP_TOGGLES = [ - ["forceLossless", "hosts.guac.forceLossless", "hosts.guac.forceLosslessDesc"], - ["enableWallpaper", "hosts.guac.wallpaper", "hosts.guac.wallpaperDesc"], - [ - "enableFontSmoothing", - "hosts.guac.fontSmoothing", - "hosts.guac.fontSmoothingDesc", - ], - [ - "enableDesktopComposition", - "hosts.guac.desktopComposition", - "hosts.guac.desktopCompositionDesc", - ], - ["disableAudio", "hosts.guac.disableAudio", "hosts.guac.disableAudioDesc"], - [ - "enablePrinting", - "hosts.guac.enablePrinting", - "hosts.guac.enablePrintingDesc", - ], - [ - "enableDrive", - "hosts.guac.enableDriveRedirection", - "hosts.guac.enableDriveRedirectionDesc", - ], - ["disableCopy", "hosts.guac.disableCopy", "hosts.guac.disableCopyDesc"], - ["disablePaste", "hosts.guac.disablePaste", "hosts.guac.disablePasteDesc"], -] as const; - function Field({ label, children, @@ -105,17 +76,14 @@ export function ConnectionDefaultsSettings() { const { t } = useTranslation(); const defaults = useConnectionDefaults(); const [open, setOpen] = useState(false); - const [tab, setTab] = useState<"shell" | "desktop">("shell"); const [terminal, setTerminal] = useState({}); - const [rdp, setRdp] = useState({}); const [saving, setSaving] = useState(false); const [savedThemes, setSavedThemes] = useState([]); useEffect(() => { if (!defaults.ready) return; setTerminal(defaults.terminal); - setRdp(defaults.rdp); - }, [defaults.ready, defaults.terminal, defaults.rdp]); + }, [defaults.ready, defaults.terminal]); useEffect(() => { if (!open) return; @@ -127,17 +95,13 @@ export function ConnectionDefaultsSettings() { const configuredCount = useMemo(() => { const count = (source: Record) => Object.values(source).filter((value) => value !== undefined).length; - return count(defaults.terminal) + count(defaults.rdp); - }, [defaults.terminal, defaults.rdp]); + return count(defaults.terminal); + }, [defaults.terminal]); const updateTerminal = ( key: K, value: TerminalDefaults[K], ) => setTerminal((current) => ({ ...current, [key]: value })); - const updateRdp = ( - key: K, - value: RemoteDesktopDefaults[K], - ) => setRdp((current) => ({ ...current, [key]: value })); const applySavedTheme = (id: string) => { if (!id) return; @@ -153,10 +117,7 @@ export function ConnectionDefaultsSettings() { async function save() { setSaving(true); try { - await Promise.all([ - defaults.saveTerminalDefaults(terminal), - defaults.saveRdpDefaults(rdp), - ]); + await defaults.saveTerminalDefaults(terminal); toast.success(t("newUi.sidebar.connectionDefaults.saved")); setOpen(false); } catch { @@ -204,241 +165,155 @@ export function ConnectionDefaultsSettings() { -
- {( - [ - ["shell", "newUi.sidebar.connectionDefaults.tabTerminal"], - [ - "desktop", - "newUi.sidebar.connectionDefaults.tabRemoteDesktop", - ], - ] as const - ).map(([id, labelKey]) => ( - - ))} -
-
- {tab === "shell" ? ( -
- - - - - - - - - {savedThemes.length > 0 && ( - - - - )} - - - - updateTerminal( - "fontSize", - e.target.value ? Number(e.target.value) : undefined, - ) - } - /> - - - - - updateTerminal( - "lineHeight", - e.target.value ? Number(e.target.value) : undefined, - ) - } - /> - - - - - - - - - updateTerminal( - "scrollback", - e.target.value ? Number(e.target.value) : undefined, - ) - } - /> - - -
- - updateTerminal("cursorBlink", value)} - /> - -
-
- ) : ( -
-
- - - - - - - -
- -
- {RDP_TOGGLES.map(([key, labelKey, descriptionKey]) => ( - - updateRdp(key, value)} - /> - +
+ + + + + + + + + {savedThemes.length > 0 && ( + + + + )} + + + + updateTerminal( + "fontSize", + e.target.value ? Number(e.target.value) : undefined, + ) + } + /> + + + + + updateTerminal( + "lineHeight", + e.target.value ? Number(e.target.value) : undefined, + ) + } + /> + + + + + + + + + updateTerminal( + "scrollback", + e.target.value ? Number(e.target.value) : undefined, + ) + } + /> + + +
+ + updateTerminal("cursorBlink", value)} + /> +
- )} +
@@ -447,7 +322,6 @@ export function ConnectionDefaultsSettings() { size="sm" onClick={() => { setTerminal({}); - setRdp({}); }} > {t("newUi.sidebar.connectionDefaults.clearAll")} diff --git a/src/ui/sidebar/HostEditor.tsx b/src/ui/sidebar/HostEditor.tsx index fd5a5ca75..376d5a800 100644 --- a/src/ui/sidebar/HostEditor.tsx +++ b/src/ui/sidebar/HostEditor.tsx @@ -76,6 +76,7 @@ import { } from "./HostEditorData"; import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext"; import { HostEditorGeneralTab } from "./HostEditorGeneralTab"; +import { withProtocolSettings } from "./host-protocols"; import { canEditHost } from "./host-permissions"; import { VaultProfileManager } from "./VaultProfileManager"; import { @@ -331,7 +332,10 @@ export function HostEditor({ } // After the host: a new one has no id to scope settings to until it // exists. A failure here must not claim the host itself failed to save. - await savePluginHostSettings(Number(saved.id), form.pluginSettings); + await savePluginHostSettings( + Number(saved.id), + withProtocolSettings(form.pluginSettings, protocols), + ); toast.success(host ? t("hosts.hostUpdated") : t("hosts.hostCreated")); setPreviewTerminalTheme(null); diff --git a/src/ui/sidebar/HostEditorData.ts b/src/ui/sidebar/HostEditorData.ts index 54d7aa669..356278c7b 100644 --- a/src/ui/sidebar/HostEditorData.ts +++ b/src/ui/sidebar/HostEditorData.ts @@ -2,19 +2,16 @@ import { TERMINAL_THEMES } from "@/lib/terminal-themes"; import type { Host } from "@/types/ui-types"; import type { SSHHostData } from "@/types"; import type { HostDefaults } from "@/api/settings-api"; -import type { - RemoteDesktopDefaults, - TerminalDefaults, -} from "@/lib/connection-defaults"; +import type { TerminalDefaults } from "@/lib/connection-defaults"; +import { + listHostProtocols, + protocolPort, + type HostProtocols, +} from "./host-protocols"; type HostSocks5ProxyNode = NonNullable[number]; -export type HostProtocols = { - enableSsh: boolean; - enableRdp: boolean; - enableVnc: boolean; - enableTelnet: boolean; -}; +export type { HostProtocols }; export type HostAuthType = Host["authType"]; export type HostCursorStyle = NonNullable< @@ -44,23 +41,8 @@ export const terminalAppearanceKeys = [ "customThemeColors", ] as const satisfies readonly (keyof TerminalDefaults)[]; -export const remoteDesktopDefaultKeys = [ - "colorDepth", - "resizeMethod", - "forceLossless", - "disableAudio", - "enableWallpaper", - "enableFontSmoothing", - "enableDesktopComposition", - "enablePrinting", - "enableDrive", - "disableCopy", - "disablePaste", -] as const satisfies readonly (keyof RemoteDesktopDefaults)[]; - export interface UserConnectionDefaults { terminal: TerminalDefaults; - rdp: RemoteDesktopDefaults; } function hasOwn(value: object | undefined, key: PropertyKey): boolean { @@ -86,18 +68,13 @@ type SnippetResponse = SnippetListItem[] | { snippets?: SnippetListItem[] }; /** * Whether the Connection Origin control is meaningful for a host. * - * Every protocol Termix can dial from either backend belongs here. RDP, VNC - * and Telnet were added once they could originate from the desktop - * (Termix-SSH/Support#1240); before that the control was gated on SSH alone, - * so a host enabling only those protocols could never reach the setting. + * Every protocol Termix can dial from either backend belongs here, plugin + * protocols included (Termix-SSH/Support#1240); before that the control was + * gated on SSH alone, so a host enabling only remote desktop could never + * reach the setting. */ export function connectionOriginAppliesTo(protocols: HostProtocols): boolean { - return ( - protocols.enableSsh || - protocols.enableRdp || - protocols.enableVnc || - protocols.enableTelnet - ); + return Object.values(protocols).some(Boolean); } export function mapSnippetResponse( @@ -122,11 +99,6 @@ export function createHostEditorForm( ...(connectionDefaults?.terminal ?? {}), ...(host?.terminalConfig ?? {}), }; - const remoteDefaults = host?.enableRdp ? connectionDefaults?.rdp : undefined; - const guacamoleConfig = { - ...(remoteDefaults ?? {}), - ...(host?.guacamoleConfig ?? {}), - }; const proxmoxSettings = (host?.pluginSettings?.proxmox ?? {}) as Record< string, unknown @@ -147,9 +119,6 @@ export function createHostEditorForm( ip: host?.ip ?? "", username: host?.username ?? (host ? "" : "root"), sshPort: host?.sshPort ?? host?.port ?? 22, - rdpPort: host?.rdpPort ?? 3389, - vncPort: host?.vncPort ?? 5900, - telnetPort: host?.telnetPort ?? 23, authType: host?.authType ?? "password", useWarpgate: host?.useWarpgate ?? false, shareSshAuth: host?.shareSshAuth ?? false, @@ -223,11 +192,6 @@ export function createHostEditorForm( inheritTerminalAppearance: !host || terminalAppearanceKeys.every((key) => !hasOwn(host.terminalConfig, key)), - inheritRemoteDesktopDefaults: - !host || - remoteDesktopDefaultKeys.every( - (key) => !hasOwn(host.guacamoleConfig, key), - ), localEcho: host?.terminalConfig?.localEcho ?? "default", fontSize: terminalConfig.fontSize ?? d?.fontSize ?? 14, fontFamily: @@ -298,8 +262,6 @@ export function createHostEditorForm( ? "existing_rdp_password" : (host?.rdpPassword ?? ""), domain: host?.domain ?? "", - security: host?.security ?? "", - ignoreCert: host?.ignoreCert ?? false, vncCredentialId: host?.vncCredentialId ?? "", vncPassword: host?.hasVncPassword ? "existing_vnc_password" @@ -320,7 +282,6 @@ export function createHostEditorForm( telnetAuthType: (host?.telnetAuthType ?? (host?.telnetCredentialId ? "credential" : "direct")) as "direct" | "credential", - guacamoleConfig, statsConfig: host?.statsConfig ?? { statusCheckEnabled: d?.statusCheckEnabled ?? true, statusCheckInterval: 60, @@ -399,9 +360,11 @@ export function buildHostEditorPayload( const usesPassword = form.authType === "password"; const usesAgent = form.authType === "agent"; const usesVault = form.authType === "vault"; - const guacamoleConfig = form.inheritRemoteDesktopDefaults - ? stripKeys(form.guacamoleConfig, remoteDesktopDefaultKeys) - : form.guacamoleConfig; + // With SSH off, the host's primary protocol is the first plugin protocol + // switched on, and its port stands in for the host port. + const primaryProtocol = protocols.enableSsh + ? undefined + : listHostProtocols().find((protocol) => protocols[protocol.settingKey]); const terminalConfig = { theme: form.theme, cursorBlink: form.cursorBlink, @@ -449,22 +412,12 @@ export function buildHostEditorPayload( : terminalConfig; return { - connectionType: protocols.enableSsh - ? "ssh" - : protocols.enableRdp - ? "rdp" - : protocols.enableVnc - ? "vnc" - : "telnet", + connectionType: primaryProtocol?.id ?? "ssh", name: form.name, ip: form.ip, - port: protocols.enableSsh - ? Number(form.sshPort) - : protocols.enableRdp - ? Number(form.rdpPort) - : protocols.enableVnc - ? Number(form.vncPort) - : Number(form.telnetPort), + port: primaryProtocol + ? protocolPort(form.pluginSettings, primaryProtocol) + : Number(form.sshPort), username: form.username, folder: form.folder, parentHostId: form.parentHostId ? Number(form.parentHostId) : null, @@ -531,13 +484,7 @@ export function buildHostEditorPayload( form.socks5ProxyMode === "chain" ? form.socks5ProxyChain : null, connectionOrigin: form.connectionOrigin, enableSsh: protocols.enableSsh, - enableRdp: protocols.enableRdp, - enableVnc: protocols.enableVnc, - enableTelnet: protocols.enableTelnet, sshPort: Number(form.sshPort), - rdpPort: Number(form.rdpPort), - vncPort: Number(form.vncPort), - telnetPort: Number(form.telnetPort), forceKeyboardInteractive: form.forceKeyboardInteractive, rdpAuthType: protocols.enableRdp ? form.rdpAuthType : null, rdpCredentialId: @@ -557,8 +504,6 @@ export function buildHostEditorPayload( ? form.rdpPassword || null : null, rdpDomain: form.domain || null, - rdpSecurity: form.security || null, - rdpIgnoreCert: form.ignoreCert, vncAuthType: protocols.enableVnc ? form.vncAuthType : null, vncCredentialId: protocols.enableVnc && @@ -602,11 +547,6 @@ export function buildHostEditorPayload( snippetId: Number(a.snippetId), })), statsConfig: form.statsConfig, - guacamoleConfig: - (protocols.enableRdp || protocols.enableVnc || protocols.enableTelnet) && - Object.keys(guacamoleConfig).length > 0 - ? guacamoleConfig - : null, terminalConfig: protocols.enableSsh ? terminalOverrides : null, }; } diff --git a/src/ui/sidebar/HostEditorGeneralTab.tsx b/src/ui/sidebar/HostEditorGeneralTab.tsx index ede2772bb..2240303d6 100644 --- a/src/ui/sidebar/HostEditorGeneralTab.tsx +++ b/src/ui/sidebar/HostEditorGeneralTab.tsx @@ -9,8 +9,6 @@ import type { Host } from "@/types/ui-types"; import { Globe, LayoutGrid, - Monitor, - MousePointerClick, Plus, Tag, Terminal, @@ -23,6 +21,7 @@ import { getSSHFolders, isElectron } from "@/main-axios"; import { connectionOriginAppliesTo } from "./HostEditorData"; import type { HostEditorForm, HostProtocols } from "./HostEditorData"; import { Select2 } from "@/components/select2"; +import { useHostProtocols } from "./host-protocols"; type HostEditorSetField = ( key: K, @@ -48,6 +47,7 @@ export function HostEditorGeneralTab({ simpleMode?: boolean; }) { const { t } = useTranslation(); + const pluginProtocols = useHostProtocols(); // Tracks which picker is shown, independent of whether a value is set yet // -- switching to "parent host" mode with nothing picked shouldn't bounce @@ -116,35 +116,22 @@ export function HostEditorGeneralTab({
{[ { - proto: "enableSsh" as const, + proto: "enableSsh", label: t("hosts.tabSsh"), desc: t("hosts.secureShell"), icon: , - portField: "sshPort" as const, - }, - { - proto: "enableRdp" as const, - label: t("hosts.tabRdp"), - desc: t("hosts.remoteDesktop"), - icon: , - portField: "rdpPort" as const, - }, - { - proto: "enableVnc" as const, - label: t("hosts.tabVnc"), - desc: t("hosts.virtualNetwork"), - icon: , - portField: "vncPort" as const, - }, - { - proto: "enableTelnet" as const, - label: t("hosts.tabTelnet"), - desc: t("hosts.unencryptedShell"), - icon: , - portField: "telnetPort" as const, }, + ...pluginProtocols.map((protocol) => { + const Icon = protocol.icon; + return { + proto: protocol.settingKey, + label: t(protocol.titleKey), + desc: protocol.descriptionKey ? t(protocol.descriptionKey) : "", + icon: , + }; + }), ].map(({ proto, label, desc, icon }) => { - const enabled = protocols[proto]; + const enabled = !!protocols[proto]; return (
- {!protocols.enableSsh && - !protocols.enableRdp && - !protocols.enableVnc && - !protocols.enableTelnet && ( -
- - {t("hosts.enableAtLeastOneProtocol")} -
- )} + {!Object.values(protocols).some(Boolean) && ( +
+ + {t("hosts.enableAtLeastOneProtocol")} +
+ )}