From 0af2c94983a2d5e9a78306117de294d0dde99d9a Mon Sep 17 00:00:00 2001 From: LukeGus Date: Thu, 24 Sep 2026 00:09:48 -0500 Subject: [PATCH] feat: turn session recording into a plugin (B13) --- docker/nginx-https.conf | 11 +- docker/nginx.conf | 11 +- drizzle/mysql/0041_wonderful_cargill.sql | 5 + drizzle/mysql/meta/0041_snapshot.json | 7689 +++++++++++++++++ drizzle/mysql/meta/_journal.json | 7 + drizzle/postgres/0042_strange_shocker.sql | 5 + drizzle/postgres/meta/0042_snapshot.json | 7493 ++++++++++++++++ drizzle/postgres/meta/_journal.json | 7 + drizzle/sqlite/0038_broad_avengers.sql | 5 + drizzle/sqlite/meta/0038_snapshot.json | 7248 ++++++++++++++++ drizzle/sqlite/meta/_journal.json | 7 + packages/plugin-sdk/ARCHITECTURE.md | 34 +- packages/plugin-sdk/FINISH-LIST.md | 39 + packages/plugin-sdk/src/backend.ts | 14 + packages/plugin-sdk/src/capabilities.ts | 1 + packages/plugin-sdk/src/testing.ts | 13 + plugins/remote-desktop/manifest.json | 7 + .../src/backend/guacamole-server.ts | 35 +- plugins/remote-desktop/src/backend/index.ts | 9 + plugins/session-recording/CHANGELOG.md | 16 + plugins/session-recording/README.md | 24 + plugins/session-recording/locales/en.json | 48 + .../locales/translated/af_ZA.json | 26 + .../locales/translated/ar_SA.json | 26 + .../locales/translated/bg_BG.json | 26 + .../locales/translated/bn_BD.json | 26 + .../locales/translated/ca_ES.json | 26 + .../locales/translated/cs_CZ.json | 26 + .../locales/translated/da_DK.json | 26 + .../locales/translated/de_DE.json | 26 + .../locales/translated/el_GR.json | 26 + .../locales/translated/es_ES.json | 26 + .../locales/translated/fi_FI.json | 26 + .../locales/translated/fr_FR.json | 26 + .../locales/translated/he_IL.json | 26 + .../locales/translated/hi_IN.json | 26 + .../locales/translated/hu_HU.json | 26 + .../locales/translated/id_ID.json | 26 + .../locales/translated/it_IT.json | 26 + .../locales/translated/ja_JP.json | 26 + .../locales/translated/ko_KR.json | 26 + .../locales/translated/nl_NL.json | 26 + .../locales/translated/no_NO.json | 26 + .../locales/translated/pl_PL.json | 26 + .../locales/translated/pt_BR.json | 26 + .../locales/translated/pt_PT.json | 26 + .../locales/translated/ro_RO.json | 26 + .../locales/translated/ru_RU.json | 26 + .../locales/translated/sr_SP.json | 26 + .../locales/translated/sv_SE.json | 26 + .../locales/translated/th_TH.json | 26 + .../locales/translated/tr_TR.json | 26 + .../locales/translated/uk_UA.json | 26 + .../locales/translated/vi_VN.json | 26 + .../locales/translated/zh_CN.json | 26 + .../locales/translated/zh_TW.json | 26 + plugins/session-recording/manifest.json | 74 + .../mysql/0001_adopt_session_recordings.sql | 22 + .../0001_adopt_session_recordings.sql | 24 + .../migrations/snapshot.json | 76 + .../sqlite/0001_adopt_session_recordings.sql | 24 + plugins/session-recording/package.json | 15 + .../session-recording/src/backend/index.ts | 42 + .../src/backend/repository.ts | 240 + .../src/backend/retention.ts | 64 + .../session-recording/src/backend/routes.ts | 253 + .../session-recording/src/backend/service.ts | 116 + .../session-recording/src/backend/tables.ts | 52 + .../src/frontend/RecordingStatus.tsx | 21 + .../src/frontend}/SessionLogsPanel.tsx | 141 +- .../src/frontend}/SessionRecordingPlayer.tsx | 11 +- .../src/frontend}/asciicast.ts | 0 .../session-recording/src/frontend/index.tsx | 42 + .../src/frontend/session-recording-api.ts | 44 + .../tests/backend/helpers.ts | 128 + .../tests/backend/migrations.test.ts | 141 + .../tests/backend/repository.test.ts | 145 + .../tests/backend/routes.test.ts | 179 + .../tests/backend/service.test.ts | 174 + .../tests/frontend/activate.test.tsx | 71 + .../tests/frontend}/asciicast.test.ts | 2 +- plugins/session-recording/tsconfig.json | 4 + plugins/session-recording/vitest.config.ts | 3 + scripts/shell-plugin-id-allowlist.json | 4 - src/backend/database/database.ts | 2 - src/backend/database/db/index.ts | 32 - .../database/db/performance-indexes.ts | 11 - src/backend/database/db/schema.mysql.ts | 42 - src/backend/database/db/schema.pg.ts | 42 - src/backend/database/db/schema.ts | 44 - src/backend/database/repositories/factory.ts | 8 - .../session-recording-repository.ts | 262 - .../database/routes/delete-user-data.ts | 9 +- .../database/routes/host-folder-routes.ts | 12 +- src/backend/database/routes/host.ts | 12 +- .../database/routes/session-log-routes.ts | 362 - src/backend/plugins/ctx.ts | 18 + src/backend/plugins/events.ts | 7 + src/backend/starter.ts | 4 + .../db/bootstrap-covers-all-tables.test.ts | 16 +- .../session-recording-repository.test.ts | 139 - .../routes/session-log-routes.test.ts | 144 - ...ssion-recording-settings-migration.test.ts | 159 + .../session-recording-settings-migration.ts | 89 + src/ui/AppShell.tsx | 11 - src/ui/api/session-log-api.ts | 75 - src/ui/locales/en.json | 24 +- src/ui/locales/translated/af_ZA.json | 20 - src/ui/locales/translated/ar_SA.json | 20 - src/ui/locales/translated/bg_BG.json | 20 - src/ui/locales/translated/bn_BD.json | 20 - src/ui/locales/translated/ca_ES.json | 20 - src/ui/locales/translated/cs_CZ.json | 20 - src/ui/locales/translated/da_DK.json | 20 - src/ui/locales/translated/de_DE.json | 20 - src/ui/locales/translated/el_GR.json | 20 - src/ui/locales/translated/es_ES.json | 20 - src/ui/locales/translated/fi_FI.json | 20 - src/ui/locales/translated/fr_FR.json | 20 - src/ui/locales/translated/he_IL.json | 20 - src/ui/locales/translated/hi_IN.json | 20 - src/ui/locales/translated/hu_HU.json | 20 - src/ui/locales/translated/id_ID.json | 20 - src/ui/locales/translated/it_IT.json | 20 - src/ui/locales/translated/ja_JP.json | 20 - src/ui/locales/translated/ko_KR.json | 20 - src/ui/locales/translated/nl_NL.json | 20 - src/ui/locales/translated/no_NO.json | 20 - src/ui/locales/translated/pl_PL.json | 20 - src/ui/locales/translated/pt_BR.json | 20 - src/ui/locales/translated/pt_PT.json | 20 - src/ui/locales/translated/ro_RO.json | 20 - src/ui/locales/translated/ru_RU.json | 20 - src/ui/locales/translated/sr_SP.json | 20 - src/ui/locales/translated/sv_SE.json | 20 - src/ui/locales/translated/th_TH.json | 20 - src/ui/locales/translated/tr_TR.json | 20 - src/ui/locales/translated/uk_UA.json | 20 - src/ui/locales/translated/vi_VN.json | 20 - src/ui/locales/translated/zh_CN.json | 20 - src/ui/locales/translated/zh_TW.json | 20 - src/ui/shell/shell-layout.ts | 2 - src/ui/shell/tabUtils.tsx | 15 - src/ui/sidebar/AppRail.tsx | 1 - src/ui/sidebar/HostEditor.tsx | 21 - src/ui/sidebar/rail-items.ts | 9 - src/ui/tests/sidebar/rail-items.test.ts | 13 +- 147 files changed, 25915 insertions(+), 2074 deletions(-) create mode 100644 drizzle/mysql/0041_wonderful_cargill.sql create mode 100644 drizzle/mysql/meta/0041_snapshot.json create mode 100644 drizzle/postgres/0042_strange_shocker.sql create mode 100644 drizzle/postgres/meta/0042_snapshot.json create mode 100644 drizzle/sqlite/0038_broad_avengers.sql create mode 100644 drizzle/sqlite/meta/0038_snapshot.json create mode 100644 plugins/session-recording/CHANGELOG.md create mode 100644 plugins/session-recording/README.md create mode 100644 plugins/session-recording/locales/en.json create mode 100644 plugins/session-recording/locales/translated/af_ZA.json create mode 100644 plugins/session-recording/locales/translated/ar_SA.json create mode 100644 plugins/session-recording/locales/translated/bg_BG.json create mode 100644 plugins/session-recording/locales/translated/bn_BD.json create mode 100644 plugins/session-recording/locales/translated/ca_ES.json create mode 100644 plugins/session-recording/locales/translated/cs_CZ.json create mode 100644 plugins/session-recording/locales/translated/da_DK.json create mode 100644 plugins/session-recording/locales/translated/de_DE.json create mode 100644 plugins/session-recording/locales/translated/el_GR.json create mode 100644 plugins/session-recording/locales/translated/es_ES.json create mode 100644 plugins/session-recording/locales/translated/fi_FI.json create mode 100644 plugins/session-recording/locales/translated/fr_FR.json create mode 100644 plugins/session-recording/locales/translated/he_IL.json create mode 100644 plugins/session-recording/locales/translated/hi_IN.json create mode 100644 plugins/session-recording/locales/translated/hu_HU.json create mode 100644 plugins/session-recording/locales/translated/id_ID.json create mode 100644 plugins/session-recording/locales/translated/it_IT.json create mode 100644 plugins/session-recording/locales/translated/ja_JP.json create mode 100644 plugins/session-recording/locales/translated/ko_KR.json create mode 100644 plugins/session-recording/locales/translated/nl_NL.json create mode 100644 plugins/session-recording/locales/translated/no_NO.json create mode 100644 plugins/session-recording/locales/translated/pl_PL.json create mode 100644 plugins/session-recording/locales/translated/pt_BR.json create mode 100644 plugins/session-recording/locales/translated/pt_PT.json create mode 100644 plugins/session-recording/locales/translated/ro_RO.json create mode 100644 plugins/session-recording/locales/translated/ru_RU.json create mode 100644 plugins/session-recording/locales/translated/sr_SP.json create mode 100644 plugins/session-recording/locales/translated/sv_SE.json create mode 100644 plugins/session-recording/locales/translated/th_TH.json create mode 100644 plugins/session-recording/locales/translated/tr_TR.json create mode 100644 plugins/session-recording/locales/translated/uk_UA.json create mode 100644 plugins/session-recording/locales/translated/vi_VN.json create mode 100644 plugins/session-recording/locales/translated/zh_CN.json create mode 100644 plugins/session-recording/locales/translated/zh_TW.json create mode 100644 plugins/session-recording/manifest.json create mode 100644 plugins/session-recording/migrations/mysql/0001_adopt_session_recordings.sql create mode 100644 plugins/session-recording/migrations/postgres/0001_adopt_session_recordings.sql create mode 100644 plugins/session-recording/migrations/snapshot.json create mode 100644 plugins/session-recording/migrations/sqlite/0001_adopt_session_recordings.sql create mode 100644 plugins/session-recording/package.json create mode 100644 plugins/session-recording/src/backend/index.ts create mode 100644 plugins/session-recording/src/backend/repository.ts create mode 100644 plugins/session-recording/src/backend/retention.ts create mode 100644 plugins/session-recording/src/backend/routes.ts create mode 100644 plugins/session-recording/src/backend/service.ts create mode 100644 plugins/session-recording/src/backend/tables.ts create mode 100644 plugins/session-recording/src/frontend/RecordingStatus.tsx rename {src/ui/sidebar => plugins/session-recording/src/frontend}/SessionLogsPanel.tsx (86%) rename {src/ui/features/session-recording => plugins/session-recording/src/frontend}/SessionRecordingPlayer.tsx (97%) rename {src/ui/features/session-recording => plugins/session-recording/src/frontend}/asciicast.ts (100%) create mode 100644 plugins/session-recording/src/frontend/index.tsx create mode 100644 plugins/session-recording/src/frontend/session-recording-api.ts create mode 100644 plugins/session-recording/tests/backend/helpers.ts create mode 100644 plugins/session-recording/tests/backend/migrations.test.ts create mode 100644 plugins/session-recording/tests/backend/repository.test.ts create mode 100644 plugins/session-recording/tests/backend/routes.test.ts create mode 100644 plugins/session-recording/tests/backend/service.test.ts create mode 100644 plugins/session-recording/tests/frontend/activate.test.tsx rename {src/ui/tests/features/session-recording => plugins/session-recording/tests/frontend}/asciicast.test.ts (88%) create mode 100644 plugins/session-recording/tsconfig.json create mode 100644 plugins/session-recording/vitest.config.ts delete mode 100644 src/backend/database/repositories/session-recording-repository.ts delete mode 100644 src/backend/database/routes/session-log-routes.ts delete mode 100644 src/backend/tests/database/repositories/session-recording-repository.test.ts delete mode 100644 src/backend/tests/database/routes/session-log-routes.test.ts create mode 100644 src/backend/tests/utils/session-recording-settings-migration.test.ts create mode 100644 src/backend/utils/crypto-migration/session-recording-settings-migration.ts delete mode 100644 src/ui/api/session-log-api.ts diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index 822b14db3..241930b7d 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -519,17 +519,10 @@ http { proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } - location /session_logs/ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - } - # File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved # to the file-manager plugin, served under /plugin-api/file-manager/. + # Session logs moved to the session-recording plugin, served under + # /plugin-api/session-recording/. location /health { proxy_pass http://127.0.0.1:30001; diff --git a/docker/nginx.conf b/docker/nginx.conf index 8ae0424cf..31962b585 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -497,17 +497,10 @@ http { proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } - location /session_logs/ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - } - # File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved # to the file-manager plugin, served under /plugin-api/file-manager/. + # Session logs moved to the session-recording plugin, served under + # /plugin-api/session-recording/. location /health { proxy_pass http://127.0.0.1:30001; diff --git a/drizzle/mysql/0041_wonderful_cargill.sql b/drizzle/mysql/0041_wonderful_cargill.sql new file mode 100644 index 000000000..ac36b6f93 --- /dev/null +++ b/drizzle/mysql/0041_wonderful_cargill.sql @@ -0,0 +1,5 @@ +-- session_recordings moved to the session-recording plugin, whose first +-- migration renames it. Dropping it here would lose every recording, so +-- this only stops drizzle tracking it. SELECT 1 because MySQL rejects an +-- empty query. +SELECT 1; diff --git a/drizzle/mysql/meta/0041_snapshot.json b/drizzle/mysql/meta/0041_snapshot.json new file mode 100644 index 000000000..6ea0fdeff --- /dev/null +++ b/drizzle/mysql/meta/0041_snapshot.json @@ -0,0 +1,7689 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "3bb6c8ff-b151-4722-b1b6-d51946ef5ba5", + "prevId": "e0d8f4e5-3151-4479-bd50-7bc5b2fdc2ce", + "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 + }, + "enable_rdp": { + "name": "enable_rdp", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_vnc": { + "name": "enable_vnc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_telnet": { + "name": "enable_telnet", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "ssh_port": { + "name": "ssh_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "rdp_port": { + "name": "rdp_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 3389 + }, + "vnc_port": { + "name": "vnc_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 5900 + }, + "telnet_port": { + "name": "telnet_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 23 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_security": { + "name": "rdp_security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_ignore_cert": { + "name": "rdp_ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "security": { + "name": "security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ignore_cert": { + "name": "ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "guacamole_config": { + "name": "guacamole_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('sha256')" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_data_id": { + "name": "ssh_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notification_channels_id": { + "name": "notification_channels_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "opkssh_tokens": { + "name": "opkssh_tokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opkssh_tokens_id": { + "name": "opkssh_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('aggregate-telemetry')" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_install_counts_id": { + "name": "plugin_install_counts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_migrations_id": { + "name": "plugin_migrations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('admin')" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_permission_grants_id": { + "name": "plugin_permission_grants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_registries_id": { + "name": "plugin_registries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_settings_id": { + "name": "plugin_settings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_storage_id": { + "name": "plugin_storage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('available')" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('disabled')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugins_id": { + "name": "plugins_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 + }, + "rdp_defaults": { + "name": "rdp_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id": { + "name": "user_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_id": { + "name": "user_roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_second_factors_id": { + "name": "user_second_factors_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('openid email profile')" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "vault_profiles": { + "name": "vault_profiles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "vault_profiles_id": { + "name": "vault_profiles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "vault_tokens": { + "name": "vault_tokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_id": { + "name": "profile_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + "user_id", + "profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "vault_tokens_id": { + "name": "vault_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "webauthn_credentials": { + "name": "webauthn_credentials", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('preferred')" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "webauthn_credentials_id": { + "name": "webauthn_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/mysql/meta/_journal.json b/drizzle/mysql/meta/_journal.json index 61e74bbe0..96608bdae 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -288,6 +288,13 @@ "when": 1790224430728, "tag": "0040_misty_skin", "breakpoints": true + }, + { + "idx": 41, + "version": "5", + "when": 1790225600497, + "tag": "0041_wonderful_cargill", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0042_strange_shocker.sql b/drizzle/postgres/0042_strange_shocker.sql new file mode 100644 index 000000000..ac36b6f93 --- /dev/null +++ b/drizzle/postgres/0042_strange_shocker.sql @@ -0,0 +1,5 @@ +-- session_recordings moved to the session-recording plugin, whose first +-- migration renames it. Dropping it here would lose every recording, so +-- this only stops drizzle tracking it. SELECT 1 because MySQL rejects an +-- empty query. +SELECT 1; diff --git a/drizzle/postgres/meta/0042_snapshot.json b/drizzle/postgres/meta/0042_snapshot.json new file mode 100644 index 000000000..cfc94de1a --- /dev/null +++ b/drizzle/postgres/meta/0042_snapshot.json @@ -0,0 +1,7493 @@ +{ + "id": "ea943776-71d3-471c-9705-cace583fddef", + "prevId": "1d75d039-d690-443a-87cb-96e79a672303", + "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 + }, + "enable_rdp": { + "name": "enable_rdp", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_vnc": { + "name": "enable_vnc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_telnet": { + "name": "enable_telnet", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 22 + }, + "rdp_port": { + "name": "rdp_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3389 + }, + "vnc_port": { + "name": "vnc_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5900 + }, + "telnet_port": { + "name": "telnet_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 23 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_security": { + "name": "rdp_security", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_ignore_cert": { + "name": "rdp_ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "security": { + "name": "security", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ignore_cert": { + "name": "ignore_cert", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "guacamole_config": { + "name": "guacamole_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + { + "expression": "parent_host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_channels": { + "name": "notification_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opkssh_tokens": { + "name": "opkssh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_install_counts": { + "name": "plugin_install_counts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_permission_grants": { + "name": "plugin_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "capability", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_registries": { + "name": "plugin_registries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_settings": { + "name": "plugin_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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 + }, + "rdp_defaults": { + "name": "rdp_defaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_second_factors": { + "name": "user_second_factors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "factor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'openid email profile'" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vault_profiles": { + "name": "vault_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vault_tokens": { + "name": "vault_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webauthn_credentials": { + "name": "webauthn_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preferred'" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/postgres/meta/_journal.json b/drizzle/postgres/meta/_journal.json index 6ca58b73b..764e9e9e4 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -295,6 +295,13 @@ "when": 1790224430003, "tag": "0041_watery_zeigeist", "breakpoints": true + }, + { + "idx": 42, + "version": "7", + "when": 1790225599928, + "tag": "0042_strange_shocker", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0038_broad_avengers.sql b/drizzle/sqlite/0038_broad_avengers.sql new file mode 100644 index 000000000..ac36b6f93 --- /dev/null +++ b/drizzle/sqlite/0038_broad_avengers.sql @@ -0,0 +1,5 @@ +-- session_recordings moved to the session-recording plugin, whose first +-- migration renames it. Dropping it here would lose every recording, so +-- this only stops drizzle tracking it. SELECT 1 because MySQL rejects an +-- empty query. +SELECT 1; diff --git a/drizzle/sqlite/meta/0038_snapshot.json b/drizzle/sqlite/meta/0038_snapshot.json new file mode 100644 index 000000000..ebe8572da --- /dev/null +++ b/drizzle/sqlite/meta/0038_snapshot.json @@ -0,0 +1,7248 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "c56e2bf7-9293-4303-9ed5-45608e58e86a", + "prevId": "da414c7f-c198-46ea-bb9b-48373ee8cbb1", + "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 + }, + "enable_rdp": { + "name": "enable_rdp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_vnc": { + "name": "enable_vnc", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_telnet": { + "name": "enable_telnet", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "rdp_port": { + "name": "rdp_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 3389 + }, + "vnc_port": { + "name": "vnc_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 5900 + }, + "telnet_port": { + "name": "telnet_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 23 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_security": { + "name": "rdp_security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_ignore_cert": { + "name": "rdp_ignore_cert", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "security": { + "name": "security", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ignore_cert": { + "name": "ignore_cert", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "guacamole_config": { + "name": "guacamole_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_socks5": { + "name": "use_socks5", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mac_address": { + "name": "mac_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wol_broadcast_address": { + "name": "wol_broadcast_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vault_profile_id_vault_profiles_id_fk": { + "name": "ssh_data_vault_profile_id_vault_profiles_id_fk", + "tableFrom": "ssh_data", + "tableTo": "vault_profiles", + "columnsFrom": [ + "vault_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_channels": { + "name": "notification_channels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "opkssh_tokens": { + "name": "opkssh_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_opkssh_tokens_user_host": { + "name": "idx_opkssh_tokens_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "opkssh_tokens_user_id_users_id_fk": { + "name": "opkssh_tokens_user_id_users_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opkssh_tokens_host_id_ssh_data_id_fk": { + "name": "opkssh_tokens_host_id_ssh_data_id_fk", + "tableFrom": "opkssh_tokens", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "rdp_defaults": { + "name": "rdp_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_oidc": { + "name": "is_oidc", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'openid email profile'" + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_enabled": { + "name": "totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_backup_codes": { + "name": "totp_backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vault_profiles": { + "name": "vault_profiles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vault_addr": { + "name": "vault_addr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vault_namespace": { + "name": "vault_namespace", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_mount": { + "name": "oidc_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_role": { + "name": "oidc_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_mount": { + "name": "ssh_mount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_role": { + "name": "ssh_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "valid_principals": { + "name": "valid_principals", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared": { + "name": "shared", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "vault_profiles_sync_id_unique": { + "name": "vault_profiles_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_profiles_user_id_users_id_fk": { + "name": "vault_profiles_user_id_users_id_fk", + "tableFrom": "vault_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vault_tokens": { + "name": "vault_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ssh_cert": { + "name": "ssh_cert", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_vault_tokens_user_profile": { + "name": "idx_vault_tokens_user_profile", + "columns": [ + "user_id", + "profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "vault_tokens_user_id_users_id_fk": { + "name": "vault_tokens_user_id_users_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "vault_tokens_profile_id_vault_profiles_id_fk": { + "name": "vault_tokens_profile_id_vault_profiles_id_fk", + "tableFrom": "vault_tokens", + "tableTo": "vault_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webauthn_credentials": { + "name": "webauthn_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backed_up": { + "name": "backed_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_verification": { + "name": "user_verification", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'preferred'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webauthn_credentials_user_id_users_id_fk": { + "name": "webauthn_credentials_user_id_users_id_fk", + "tableFrom": "webauthn_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/sqlite/meta/_journal.json b/drizzle/sqlite/meta/_journal.json index aaee8cd66..d2e698d0b 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -267,6 +267,13 @@ "when": 1790224429343, "tag": "0037_burly_enchantress", "breakpoints": true + }, + { + "idx": 38, + "version": "6", + "when": 1790225599344, + "tag": "0038_broad_avengers", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/plugin-sdk/ARCHITECTURE.md b/packages/plugin-sdk/ARCHITECTURE.md index 7f2434db5..767d13d77 100644 --- a/packages/plugin-sdk/ARCHITECTURE.md +++ b/packages/plugin-sdk/ARCHITECTURE.md @@ -118,7 +118,11 @@ Plugins own their tables. A plugin declares them with `defineTable()` from `@termix/plugin-sdk/db` and ships migrations for `sqlite`, `postgres` and `mysql` under `migrations//NNNN_name.sql`. Table names are prefixed `p__`, which `defineTable` adds. `ctx.kv` is for small -key/value state. Core `schema.ts` ends up holding only core tables. +key/value state, and `ctx.files.dataDir()` (**B13**, needs `files:own`) is a +per-plugin folder under `DATA_DIR`, created on first call, for state too +large for `ctx.kv` (session recordings, uploaded files); a plugin lays out +its own subdirectories underneath it. Core `schema.ts` ends up holding only +core tables. **One table object, three DDL emitters.** The sqlite-core definitions already encode correctly on every engine at query time, which is why core's own 44 @@ -608,6 +612,22 @@ the asciicast formatting and the 300 ms batched flush (issue #1049) and hands the provider one `append` per batch. Service names need a dot, so "tmux" and "recordings" became `tmux.sessions` and `recordings.writer`. +**B13** is the session-recording plugin, which provides `recordings.writer`. +It adopts `session_recordings`, keeping `userId` a plain column rather than +`refUser()` (a recording is evidence about the host as much as the person, +so it outlives the account) and clearing it on the `user.deleted` event +instead of cascading. `open(meta)` returns null when the host's +`enableSessionRecording` setting is off; otherwise a `RecordingSink` with +`append(chunk)` (one call per already-batched chunk, so it never writes +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 +under `ctx.files.dataDir()/session_logs//.cast`. Retention +(`retentionDays`, an admin setting) runs a sweep at boot and every 24 hours. + A service call runs a permission check for its actor, so a share-link guest, who has no user, cannot make one. Guest link resolution is therefore published on `ctx.registry` as `sessions.sharing.guests` instead: the token is @@ -1161,6 +1181,7 @@ deciding, not the mechanism. | `hosts:read` | low | See the host list it can already see | | `db:own` | low | Own tables | | `kv:own` | low | Small key/value state | +| `files:own` | low | Its own files on disk | | `secrets:own` | low | Its own encrypted secrets | | `settings:read-core` | low | Read core server settings | | `ui:surface` | low | Contribute tabs, panels and settings | @@ -1190,6 +1211,7 @@ Built per plugin in `src/backend/plugins/ctx.ts` and passed to `activate`. | `ctx.log.*` | none | **A1** | | `ctx.events.emit` / `.on` | `events:core` for core topics | **A1** | | `ctx.kv.get/set/delete/list` | `kv:own` | **A1** | +| `ctx.files.dataDir` | `files:own` | **B13** | | `ctx.registry.*` | none | **A1** | | `ctx.services.provide` / `.get` / `.providers` | per-service RBAC | **A1**, named **B12** | | `ctx.secrets.offer` / `.withdraw` / `.getShared` | per-secret RBAC | **A1** | @@ -1668,9 +1690,13 @@ upgrade on all three engines, and the checks in step 15 are green. - `src/backend/database/routes/delete-user-data.ts` and the user/host repositories: explicit deletes of the table. A `refUser`/`refHost` column cascades on every engine, because the foreign key survives the rename. - Prefer that cascade. A table that cannot carry such a column needs a - core "user deleted" or "host deleted" event on `ctx.events`, added to - the SDK the step 3 way; none exists yet because no plugin needed one. + Prefer that cascade. A table that cannot carry such a column (evidence + that has to outlive the account, like a recording) subscribes to + `ctx.events.on("user.deleted", ...)` or `"host.deleted"` instead, added + to the SDK the step 3 way; **B13** added `user.deleted` + (`TOPICS.userDeleted` in `src/backend/plugins/events.ts`), emitted from + `deleteUserAndRelatedData` right where the old direct repository call + used to sit. `host.deleted` already existed (B7). - `sync-entities.ts`, if core synced it (step 9). - Anything else `grep -rn "\|" src` finds, including core tests and `src/types`. diff --git a/packages/plugin-sdk/FINISH-LIST.md b/packages/plugin-sdk/FINISH-LIST.md index 2400b9b5f..6f68865cf 100644 --- a/packages/plugin-sdk/FINISH-LIST.md +++ b/packages/plugin-sdk/FINISH-LIST.md @@ -282,6 +282,40 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite desktop toolbars now; the tab bar's share button is gone, so a host with the terminal toolbar switched off has no share button. A generic tab-menu slot would bring it back if that matters. Owner: D1, if wanted. +- **B13 (session-recording):** `enable_session_logging` is still a live + `ssh_data` column; only the copy-into-plugin-settings migration shipped + (`session-recording-settings-migration.ts`). The legacy default was `true`; + the plugin's enable switch defaults to `false` like every other plugin's, so + the migration copies every host's value rather than only the ones that + turned it off - a fresh host created after 2.9.0 defaults to recording off, + 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`, + `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 + data plumbing above was not switched over. Dropping the column in lockstep + (`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 + `defineTable` has no builder for a foreign key into an arbitrary core + table beyond `refUser()`/`refHost()`. Nothing has ever populated it (true + in core before this step too). Owner: none needed unless a future step + wants to actually use it. ## Manual checks after 2.9.0 @@ -295,3 +329,8 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite window, a user share joined from Active Connections, revoke and "end for everyone", and a collab room with two members, control hand-off, a guest link, rotate and end. +- Session recording: an SSH session recorded end to end (playback matches what + happened, including a resize mid-session), a host with recording switched + 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. diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index 2f8d2f5fd..8c8509dfb 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -74,6 +74,19 @@ export interface PluginKeyValue { list: () => Promise; } +/** + * A folder on disk this plugin owns, for state too large for ctx.kv (session + * recordings, uploaded files). Requires files:own. + */ +export interface PluginFiles { + /** + * The plugin's own folder under DATA_DIR, created if it does not exist yet. + * Always the same path for this plugin; a plugin lays out its own + * subdirectories underneath it. + */ + dataDir: () => Promise; +} + /** * The plugin's own tables. * @@ -1147,6 +1160,7 @@ export interface PluginContext { readonly log: PluginLogger; readonly events: PluginEvents; readonly kv: PluginKeyValue; + readonly files: PluginFiles; readonly db: PluginDatabase; readonly sync: PluginSync; readonly registry: PluginRegistry; diff --git a/packages/plugin-sdk/src/capabilities.ts b/packages/plugin-sdk/src/capabilities.ts index 0cafcde52..51b884454 100644 --- a/packages/plugin-sdk/src/capabilities.ts +++ b/packages/plugin-sdk/src/capabilities.ts @@ -60,6 +60,7 @@ export const CAPABILITY_CATALOG: readonly CapabilityInfo[] = [ entry("hosts:read", "low"), entry("db:own", "low"), entry("kv:own", "low"), + entry("files:own", "low"), entry("secrets:own", "low"), entry("settings:read-core", "low"), entry("ui:surface", "low"), diff --git a/packages/plugin-sdk/src/testing.ts b/packages/plugin-sdk/src/testing.ts index fea1f436a..4667d3247 100644 --- a/packages/plugin-sdk/src/testing.ts +++ b/packages/plugin-sdk/src/testing.ts @@ -300,6 +300,12 @@ export function createFakeContext( list: async () => [...kv.keys()], }, + files: { + // A fake path; a test that needs a real folder uses the OS temp dir + // itself, not this double. + dataDir: async () => `/tmp/${pluginId}`, + }, + db: options.db ? { ...options.db, @@ -798,6 +804,13 @@ export function createMockCtx( }, }, + files: { + dataDir: async () => { + require("files:own"); + return ctx.files.dataDir(); + }, + }, + events: { emit: (topic, payload) => { if ( diff --git a/plugins/remote-desktop/manifest.json b/plugins/remote-desktop/manifest.json index 68fe72225..119307b36 100644 --- a/plugins/remote-desktop/manifest.json +++ b/plugins/remote-desktop/manifest.json @@ -22,6 +22,13 @@ "audit:read", "ui:surface" ], + "requires": [ + { + "service": "recordings.writer", + "versionRange": "^1.0.0", + "optional": true + } + ], "contributes": { "tabs": [ { diff --git a/plugins/remote-desktop/src/backend/guacamole-server.ts b/plugins/remote-desktop/src/backend/guacamole-server.ts index 5e759d238..8887c724c 100644 --- a/plugins/remote-desktop/src/backend/guacamole-server.ts +++ b/plugins/remote-desktop/src/backend/guacamole-server.ts @@ -4,16 +4,33 @@ import { GuacamoleTokenService, type GuacamoleRecordingMetadata, } from "./token-service.js"; -import { - createCurrentSessionRecordingRepository, - getCurrentSettingValue, -} from "../../../../src/backend/database/repositories/factory.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(); +/** The recordings.writer service, when the session-recording plugin provides it. */ +export interface RecordingsWriter { + createFinished: (input: { + hostId: number; + userId: string; + startedAt: string; + endedAt: string; + duration: number | null; + recordingPath: string; + protocol: string; + format: string; + }) => 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 { @@ -103,9 +120,17 @@ async function persistGuacamoleRecording( 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 createCurrentSessionRecordingRepository().create({ + await pluginRecordings.createFinished({ hostId: recording.hostId, userId: recording.userId, startedAt: startedAt.toISOString(), diff --git a/plugins/remote-desktop/src/backend/index.ts b/plugins/remote-desktop/src/backend/index.ts index c24fca772..cc7f1416e 100644 --- a/plugins/remote-desktop/src/backend/index.ts +++ b/plugins/remote-desktop/src/backend/index.ts @@ -4,15 +4,24 @@ import { getGuacSessionInfo, handleGuacamoleUpgrade, restartGuacServer, + setPluginRecordings, startGuacamoleService, stopGuacamoleService, tokenService, + type RecordingsWriter, } from "./guacamole-server.js"; import { startRemoteDesktopService } from "./routes.js"; export async function activate(ctx: PluginContext) { setPluginSsh(ctx.ssh); ctx.disposables.add(() => setPluginSsh(null)); + + // 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)); + await startGuacamoleService(); startRemoteDesktopService(ctx.http.router()); diff --git a/plugins/session-recording/CHANGELOG.md b/plugins/session-recording/CHANGELOG.md new file mode 100644 index 000000000..20c363a29 --- /dev/null +++ b/plugins/session-recording/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## 1.0.0 + +Bundled with Termix 2.9.0. + +- Session recording moved out of core's `/session_logs` routes and now serves + under `/plugin-api/session-recording` through core's connect pipeline. +- Owns its data: core's `session_recordings` table is renamed to + `p_session_recording_session_recordings` on first activation. +- New `session-recording.view` permission, given to the admin and user roles. +- Offers `recordings.writer` to other plugins: `open()`/`append()`/`persist()` + for an incremental writer (ssh-terminal), `createFinished()` for a recording + already written to disk (remote desktop's guacd recordings). +- Retention days becomes an admin plugin setting; per-host recording becomes + a host plugin setting. diff --git a/plugins/session-recording/README.md b/plugins/session-recording/README.md new file mode 100644 index 000000000..0ce390b03 --- /dev/null +++ b/plugins/session-recording/README.md @@ -0,0 +1,24 @@ +# Session Recording + +Records terminal sessions and lets you play them back or download them. + +Bundled with Termix and enabled by default. It imports only +`@termix/plugin-sdk`. + +## What it does + +- **Recording**: an SSH terminal session is recorded as an asciicast, written + incrementally in 300ms batches as it happens (issue #1049). +- **Playback**: view a recording inline, scrub through it, copy its plain + text, or download it as a recording file or plain text. +- **Retention**: an admin setting prunes recordings older than a configured + number of days, checked at startup and every 24 hours. +- **Per-host switch**: recording can be turned off for a host. +- **`recordings.writer`**: a service other plugins consume. `open(meta)` + starts an incremental recording (ssh-terminal); `createFinished(input)` + inserts a row for a recording the caller already wrote to disk itself + (remote desktop's guacd recordings). + +Recordings outlive the account they were made under: deleting a user +anonymizes their recordings rather than deleting them, since a recording is +evidence about the host as much as about the person. diff --git a/plugins/session-recording/locales/en.json b/plugins/session-recording/locales/en.json new file mode 100644 index 000000000..9f11b7976 --- /dev/null +++ b/plugins/session-recording/locales/en.json @@ -0,0 +1,48 @@ +{ + "nav": { + "sessionLogs": "Session Logs" + }, + "hosts": { + "enableSessionRecording": "Session Recording", + "enableSessionRecordingDesc": "Record terminal session output for later review" + }, + "permissions": { + "view": { + "title": "View session recordings", + "description": "See and play back your own session recordings." + } + }, + "settings": { + "admin": { + "retentionDays": { + "label": "Retention (days)", + "description": "How long a session recording is kept before it is deleted automatically." + } + }, + "host": { + "enable": { + "label": "Enable session recording" + } + } + }, + "terminalStatus": { + "recording": "Recording" + }, + "sessionLogs": { + "title": "Session Logs", + "noLogs": "No session logs yet", + "noLogsDesc": "Enable session logging on a host to start recording", + "duration": "Duration", + "viewLog": "View log", + "downloadLog": "Download", + "downloadAsText": "Download as text", + "deleteLog": "Delete", + "confirmDelete": "Delete this session log?", + "confirmDeleteDesc": "This action cannot be undone.", + "copyContent": "Copy", + "copied": "Copied!", + "loadError": "Failed to load session logs", + "deleteError": "Failed to delete session log", + "filterByHost": "Filter by host..." + } +} diff --git a/plugins/session-recording/locales/translated/af_ZA.json b/plugins/session-recording/locales/translated/af_ZA.json new file mode 100644 index 000000000..16ab3d14d --- /dev/null +++ b/plugins/session-recording/locales/translated/af_ZA.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Sessielogboeke" + }, + "hosts": { + "enableSessionRecording": "Sessie-logging", + "enableSessionRecordingDesc": "Neem terminaalsessie-uitvoer op vir latere hersiening" + }, + "sessionLogs": { + "title": "Sessielogboeke", + "noLogs": "Geen sessielogboeke nog nie", + "noLogsDesc": "Aktiveer sessie-aanmelding op 'n gasheer om opname te begin", + "duration": "Duur", + "viewLog": "Bekyk logboek", + "downloadLog": "Laai af", + "downloadAsText": "Laai af as teks", + "deleteLog": "Vee uit", + "confirmDelete": "Vee hierdie sessielog uit?", + "confirmDeleteDesc": "Hierdie aksie kan nie ongedaan gemaak word nie.", + "copyContent": "Kopieer", + "copied": "Gekopieer!", + "loadError": "Kon nie sessielogboeke laai nie", + "deleteError": "Kon nie sessielogboek uitvee nie", + "filterByHost": "Filtreer volgens gasheer..." + } +} diff --git a/plugins/session-recording/locales/translated/ar_SA.json b/plugins/session-recording/locales/translated/ar_SA.json new file mode 100644 index 000000000..fd6bb7040 --- /dev/null +++ b/plugins/session-recording/locales/translated/ar_SA.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "سجلات الجلسات" + }, + "hosts": { + "enableSessionRecording": "تسجيل الجلسات", + "enableSessionRecordingDesc": "تسجيل مخرجات الجلسة الطرفية للمراجعة لاحقاً" + }, + "sessionLogs": { + "title": "سجلات الجلسات", + "noLogs": "لا توجد سجلات جلسات بعد", + "noLogsDesc": "فعّل تسجيل الجلسات على مضيف لبدء التسجيل", + "duration": "المدة", + "viewLog": "عرض السجل", + "downloadLog": "تنزيل", + "downloadAsText": "تنزيل كنص", + "deleteLog": "حذف", + "confirmDelete": "حذف سجل الجلسة هذا؟", + "confirmDeleteDesc": "لا يمكن التراجع عن هذا الإجراء.", + "copyContent": "نسخ", + "copied": "تم النسخ!", + "loadError": "فشل في تحميل سجلات الجلسات", + "deleteError": "فشل في حذف سجل الجلسة", + "filterByHost": "تصفية حسب المضيف..." + } +} diff --git a/plugins/session-recording/locales/translated/bg_BG.json b/plugins/session-recording/locales/translated/bg_BG.json new file mode 100644 index 000000000..ccca07d8a --- /dev/null +++ b/plugins/session-recording/locales/translated/bg_BG.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Дневници на сесиите" + }, + "hosts": { + "enableSessionRecording": "Записване на сесии", + "enableSessionRecordingDesc": "Записване на изхода от терминалната сесия за по-късен преглед" + }, + "sessionLogs": { + "title": "Дневници на сесии", + "noLogs": "Все още няма логове на сесии", + "noLogsDesc": "Активирайте регистрирането на сесията на хост, за да започнете записа", + "duration": "Продължителност", + "viewLog": "Преглед на дневника", + "downloadLog": "Изтегляне", + "downloadAsText": "Изтегляне като текст", + "deleteLog": "Изтриване", + "confirmDelete": "Да се изтрие ли този дневник на сесията?", + "confirmDeleteDesc": "Това действие не може да бъде отменено.", + "copyContent": "Копиране", + "copied": "Копирано!", + "loadError": "Зареждането на лог файловете на сесията не бе успешно", + "deleteError": "Неуспешно изтриване на дневника на сесията", + "filterByHost": "Филтриране по хост..." + } +} diff --git a/plugins/session-recording/locales/translated/bn_BD.json b/plugins/session-recording/locales/translated/bn_BD.json new file mode 100644 index 000000000..51db7504a --- /dev/null +++ b/plugins/session-recording/locales/translated/bn_BD.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "সেশন লগ" + }, + "hosts": { + "enableSessionRecording": "সেশন লগিং", + "enableSessionRecordingDesc": "পরবর্তী পর্যালোচনার জন্য টার্মিনাল সেশনের আউটপুট রেকর্ড করুন।" + }, + "sessionLogs": { + "title": "সেশন লগ", + "noLogs": "এখনো কোনো সেশন লগ নেই", + "noLogsDesc": "রেকর্ডিং শুরু করতে কোনো হোস্টে সেশন লগিং চালু করুন।", + "duration": "সময়কাল", + "viewLog": "লগ দেখুন", + "downloadLog": "ডাউনলোড করুন", + "downloadAsText": "টেক্সট হিসেবে ডাউনলোড করুন", + "deleteLog": "মুছে ফেলুন", + "confirmDelete": "এই সেশন লগটি মুছে ফেলবেন?", + "confirmDeleteDesc": "এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।", + "copyContent": "অনুলিপি", + "copied": "অনুলিপি করা হয়েছে!", + "loadError": "সেশন লগ লোড করতে ব্যর্থ হয়েছে", + "deleteError": "সেশন লগ মুছে ফেলতে ব্যর্থ হয়েছে", + "filterByHost": "হোস্ট দ্বারা ফিল্টার করুন..." + } +} diff --git a/plugins/session-recording/locales/translated/ca_ES.json b/plugins/session-recording/locales/translated/ca_ES.json new file mode 100644 index 000000000..ff40a37e4 --- /dev/null +++ b/plugins/session-recording/locales/translated/ca_ES.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Registres de sessió" + }, + "hosts": { + "enableSessionRecording": "Registre de sessions", + "enableSessionRecordingDesc": "Enregistra la sortida de la sessió de terminal per a una revisió posterior" + }, + "sessionLogs": { + "title": "Registres de sessió", + "noLogs": "Encara no hi ha registres de sessió", + "noLogsDesc": "Habilita el registre de sessions en un amfitrió per iniciar l'enregistrament", + "duration": "Durada", + "viewLog": "Veure registre", + "downloadLog": "Descarrega", + "downloadAsText": "Descarrega com a text", + "deleteLog": "Suprimeix", + "confirmDelete": "Voleu suprimir aquest registre de sessió?", + "confirmDeleteDesc": "Aquesta acció no es pot desfer.", + "copyContent": "Còpia", + "copied": "Copiat!", + "loadError": "No s'han pogut carregar els registres de sessió", + "deleteError": "No s'ha pogut suprimir el registre de sessió", + "filterByHost": "Filtra per amfitrió..." + } +} diff --git a/plugins/session-recording/locales/translated/cs_CZ.json b/plugins/session-recording/locales/translated/cs_CZ.json new file mode 100644 index 000000000..2d52fdb85 --- /dev/null +++ b/plugins/session-recording/locales/translated/cs_CZ.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Protokoly relací" + }, + "hosts": { + "enableSessionRecording": "Protokolování relací", + "enableSessionRecordingDesc": "Zaznamenejte výstup terminálové relace pro pozdější kontrolu" + }, + "sessionLogs": { + "title": "Protokoly relací", + "noLogs": "Zatím žádné protokoly relací", + "noLogsDesc": "Povolte protokolování relace na hostiteli pro zahájení nahrávání", + "duration": "Trvání", + "viewLog": "Zobrazit protokol", + "downloadLog": "Stáhnout", + "downloadAsText": "Stáhnout jako text", + "deleteLog": "Vymazat", + "confirmDelete": "Smazat tento protokol relace?", + "confirmDeleteDesc": "Tuto akci nelze vrátit zpět.", + "copyContent": "Kopie", + "copied": "Zkopírováno!", + "loadError": "Nepodařilo se načíst protokoly relací", + "deleteError": "Nepodařilo se smazat protokol relace", + "filterByHost": "Filtrovat podle hostitele..." + } +} diff --git a/plugins/session-recording/locales/translated/da_DK.json b/plugins/session-recording/locales/translated/da_DK.json new file mode 100644 index 000000000..3e993b79d --- /dev/null +++ b/plugins/session-recording/locales/translated/da_DK.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Sessionslogfiler" + }, + "hosts": { + "enableSessionRecording": "Sessionslogning", + "enableSessionRecordingDesc": "Optag output fra terminalsessionen til senere gennemgang" + }, + "sessionLogs": { + "title": "Sessionslogfiler", + "noLogs": "Ingen sessionslogfiler endnu", + "noLogsDesc": "Aktivér sessionslogning på en vært for at starte optagelse", + "duration": "Varighed", + "viewLog": "Se log", + "downloadLog": "Download", + "downloadAsText": "Download som tekst", + "deleteLog": "Slet", + "confirmDelete": "Slet denne sessionslog?", + "confirmDeleteDesc": "Denne handling kan ikke fortrydes.", + "copyContent": "Kopi", + "copied": "Kopieret!", + "loadError": "Kunne ikke indlæse sessionslogfiler", + "deleteError": "Kunne ikke slette sessionsloggen", + "filterByHost": "Filtrer efter vært..." + } +} diff --git a/plugins/session-recording/locales/translated/de_DE.json b/plugins/session-recording/locales/translated/de_DE.json new file mode 100644 index 000000000..60477e4b2 --- /dev/null +++ b/plugins/session-recording/locales/translated/de_DE.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Sitzungsprotokolle" + }, + "hosts": { + "enableSessionRecording": "Sitzungsprotokollierung", + "enableSessionRecordingDesc": "Terminal-Sitzungsausgabe zur späteren Überprüfung aufzeichnen" + }, + "sessionLogs": { + "title": "Sitzungsprotokolle", + "noLogs": "Noch keine Sitzungsprotokolle", + "noLogsDesc": "Aktivieren Sie die Sitzungsprotokollierung auf einem Host, um die Aufzeichnung zu starten", + "duration": "Dauer", + "viewLog": "Protokoll anzeigen", + "downloadLog": "Herunterladen", + "downloadAsText": "Als Text herunterladen", + "deleteLog": "Löschen", + "confirmDelete": "Dieses Sitzungsprotokoll löschen?", + "confirmDeleteDesc": "Diese Aktion kann nicht rückgängig gemacht werden.", + "copyContent": "Kopieren", + "copied": "Kopiert!", + "loadError": "Fehler beim Laden der Sitzungsprotokolle", + "deleteError": "Fehler beim Löschen des Sitzungsprotokolls", + "filterByHost": "Nach Host filtern..." + } +} diff --git a/plugins/session-recording/locales/translated/el_GR.json b/plugins/session-recording/locales/translated/el_GR.json new file mode 100644 index 000000000..e9c0a023c --- /dev/null +++ b/plugins/session-recording/locales/translated/el_GR.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Αρχεία καταγραφής συνεδρίας" + }, + "hosts": { + "enableSessionRecording": "Καταγραφή συνεδρίας", + "enableSessionRecordingDesc": "Καταγραφή εξόδου συνεδρίας τερματικού για μελλοντική αναθεώρηση" + }, + "sessionLogs": { + "title": "Αρχεία καταγραφής συνεδρίας", + "noLogs": "Δεν υπάρχουν ακόμη αρχεία καταγραφής συνεδρίας", + "noLogsDesc": "Ενεργοποίηση καταγραφής περιόδου λειτουργίας σε έναν κεντρικό υπολογιστή για έναρξη εγγραφής", + "duration": "Διάρκεια", + "viewLog": "Προβολή αρχείου καταγραφής", + "downloadLog": "Λήψη", + "downloadAsText": "Λήψη ως κείμενο", + "deleteLog": "Διαγράφω", + "confirmDelete": "Διαγραφή αυτού του αρχείου καταγραφής περιόδου σύνδεσης;", + "confirmDeleteDesc": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "copyContent": "Αντίγραφο", + "copied": "Αντιγράφηκε!", + "loadError": "Αποτυχία φόρτωσης αρχείων καταγραφής περιόδου λειτουργίας", + "deleteError": "Η διαγραφή του αρχείου καταγραφής περιόδου λειτουργίας απέτυχε", + "filterByHost": "Φιλτράρισμα κατά κεντρικό υπολογιστή..." + } +} diff --git a/plugins/session-recording/locales/translated/es_ES.json b/plugins/session-recording/locales/translated/es_ES.json new file mode 100644 index 000000000..193831004 --- /dev/null +++ b/plugins/session-recording/locales/translated/es_ES.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Registros de sesión" + }, + "hosts": { + "enableSessionRecording": "Registro de sesión", + "enableSessionRecordingDesc": "Registrar la salida de la sesión de terminal para su revisión posterior" + }, + "sessionLogs": { + "title": "Registros de sesión", + "noLogs": "Aún no hay registros de sesión", + "noLogsDesc": "Activa el registro de sesiones en un host para empezar a grabar", + "duration": "Duración", + "viewLog": "Ver registro", + "downloadLog": "Descargar", + "downloadAsText": "Descargar como texto", + "deleteLog": "Eliminar", + "confirmDelete": "¿Eliminar este registro de sesión?", + "confirmDeleteDesc": "Esta acción no se puede deshacer.", + "copyContent": "Copiar", + "copied": "¡Copiado!", + "loadError": "Error al cargar los registros de sesión", + "deleteError": "Error al eliminar el registro de sesión", + "filterByHost": "Filtrar por host..." + } +} diff --git a/plugins/session-recording/locales/translated/fi_FI.json b/plugins/session-recording/locales/translated/fi_FI.json new file mode 100644 index 000000000..00d704b1e --- /dev/null +++ b/plugins/session-recording/locales/translated/fi_FI.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Istuntolokit" + }, + "hosts": { + "enableSessionRecording": "Istuntojen lokitiedot", + "enableSessionRecordingDesc": "Tallenna pääteistunnon tuloste myöhempää tarkastelua varten" + }, + "sessionLogs": { + "title": "Istuntolokit", + "noLogs": "Ei vielä istuntolokeja", + "noLogsDesc": "Ota istunnon lokikirjaus käyttöön isännässä aloittaaksesi tallennuksen", + "duration": "Kesto", + "viewLog": "Näytä loki", + "downloadLog": "Lataa", + "downloadAsText": "Lataa tekstinä", + "deleteLog": "Poistaa", + "confirmDelete": "Poistetaanko tämä istuntoloki?", + "confirmDeleteDesc": "Tätä toimintoa ei voi perua.", + "copyContent": "Kopioida", + "copied": "Kopioitu!", + "loadError": "Istuntolokien lataaminen epäonnistui", + "deleteError": "Istuntolokitietojen poistaminen epäonnistui", + "filterByHost": "Suodata isännän mukaan..." + } +} diff --git a/plugins/session-recording/locales/translated/fr_FR.json b/plugins/session-recording/locales/translated/fr_FR.json new file mode 100644 index 000000000..c1b5bc9bf --- /dev/null +++ b/plugins/session-recording/locales/translated/fr_FR.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Journaux de session" + }, + "hosts": { + "enableSessionRecording": "Journalisation de session", + "enableSessionRecordingDesc": "Enregistrer la sortie de la session terminal pour une consultation ultérieure" + }, + "sessionLogs": { + "title": "Journaux de session", + "noLogs": "Aucun journal de session pour l’instant", + "noLogsDesc": "Activez la journalisation des sessions sur un hôte pour démarrer l’enregistrement", + "duration": "Durée", + "viewLog": "Afficher le journal", + "downloadLog": "Télécharger", + "downloadAsText": "Télécharger au format texte", + "deleteLog": "Supprimer", + "confirmDelete": "Supprimer ce journal de session ?", + "confirmDeleteDesc": "Cette action est irréversible.", + "copyContent": "Copier", + "copied": "Copié !", + "loadError": "Échec du chargement des journaux de session", + "deleteError": "Échec de la suppression du journal de session", + "filterByHost": "Filtrer par hôte..." + } +} diff --git a/plugins/session-recording/locales/translated/he_IL.json b/plugins/session-recording/locales/translated/he_IL.json new file mode 100644 index 000000000..6afc6c266 --- /dev/null +++ b/plugins/session-recording/locales/translated/he_IL.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "יומני סשן" + }, + "hosts": { + "enableSessionRecording": "רישום מושבים", + "enableSessionRecordingDesc": "הקלט את פלט הפעלת הטרמינל לצורך סקירה מאוחרת יותר" + }, + "sessionLogs": { + "title": "יומני סשן", + "noLogs": "אין עדיין יומני סשן", + "noLogsDesc": "הפעל רישום סשן במארח כדי להתחיל להקליט", + "duration": "מֶשֶׁך", + "viewLog": "הצג יומן", + "downloadLog": "הורדה", + "downloadAsText": "הורד כטקסט", + "deleteLog": "לִמְחוֹק", + "confirmDelete": "למחוק את יומן הפעילויות הזה?", + "confirmDeleteDesc": "לא ניתן לבטל פעולה זו.", + "copyContent": "לְהַעְתִיק", + "copied": "מוּעֲתָק!", + "loadError": "טעינת יומני הסשן נכשלה", + "deleteError": "נכשלה מחיקת יומן ההפעלה", + "filterByHost": "סנן לפי מארח..." + } +} diff --git a/plugins/session-recording/locales/translated/hi_IN.json b/plugins/session-recording/locales/translated/hi_IN.json new file mode 100644 index 000000000..180486963 --- /dev/null +++ b/plugins/session-recording/locales/translated/hi_IN.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "सत्र लॉग्स" + }, + "hosts": { + "enableSessionRecording": "सत्र लॉगिंग", + "enableSessionRecordingDesc": "बाद में समीक्षा के लिए टर्मिनल सत्र का आउटपुट रिकॉर्ड करें" + }, + "sessionLogs": { + "title": "सेशन लॉग्स", + "noLogs": "अभी तक कोई सेशन लॉग नहीं", + "noLogsDesc": "रिकॉर्डिंग शुरू करने के लिए किसी होस्ट पर सेशन लॉगिंग सक्षम करें", + "duration": "अवधि", + "viewLog": "लॉग देखें", + "downloadLog": "डाउनलोड करें", + "downloadAsText": "टेक्स्ट के रूप में डाउनलोड करें", + "deleteLog": "हटाएं", + "confirmDelete": "क्या इस सेशन लॉग को हटाएं?", + "confirmDeleteDesc": "यह क्रिया वापस नहीं ली जा सकती।", + "copyContent": "कॉपी करें", + "copied": "कॉपी हो गया!", + "loadError": "सेशन लॉग लोड करने में विफल", + "deleteError": "सेशन लॉग हटाने में विफल", + "filterByHost": "होस्ट द्वारा फ़िल्टर करें..." + } +} diff --git a/plugins/session-recording/locales/translated/hu_HU.json b/plugins/session-recording/locales/translated/hu_HU.json new file mode 100644 index 000000000..a4becc382 --- /dev/null +++ b/plugins/session-recording/locales/translated/hu_HU.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Munkamenet-naplók" + }, + "hosts": { + "enableSessionRecording": "Munkamenet-naplózás", + "enableSessionRecordingDesc": "Terminál munkamenet kimenetének rögzítése későbbi áttekintés céljából" + }, + "sessionLogs": { + "title": "Munkamenet-naplók", + "noLogs": "Még nincsenek munkamenet-naplók", + "noLogsDesc": "A felvétel megkezdéséhez engedélyezze a munkamenet-naplózást egy gazdagépen", + "duration": "Időtartam", + "viewLog": "Napló megtekintése", + "downloadLog": "Letöltés", + "downloadAsText": "Letöltés szövegként", + "deleteLog": "Töröl", + "confirmDelete": "Törli ezt a munkamenet-naplót?", + "confirmDeleteDesc": "Ez a művelet nem vonható vissza.", + "copyContent": "Másolat", + "copied": "Másolva!", + "loadError": "Nem sikerült betölteni a munkamenet-naplókat", + "deleteError": "Nem sikerült törölni a munkamenet-naplót", + "filterByHost": "Szűrés hoszt szerint..." + } +} diff --git a/plugins/session-recording/locales/translated/id_ID.json b/plugins/session-recording/locales/translated/id_ID.json new file mode 100644 index 000000000..1109f3875 --- /dev/null +++ b/plugins/session-recording/locales/translated/id_ID.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Log Sesi" + }, + "hosts": { + "enableSessionRecording": "Pencatatan Sesi", + "enableSessionRecordingDesc": "Rekam output sesi terminal untuk ditinjau nanti." + }, + "sessionLogs": { + "title": "Log Sesi", + "noLogs": "Belum ada log sesi.", + "noLogsDesc": "Aktifkan pencatatan sesi pada host untuk memulai perekaman.", + "duration": "Lamanya", + "viewLog": "Lihat log", + "downloadLog": "Unduh", + "downloadAsText": "Unduh sebagai teks", + "deleteLog": "Menghapus", + "confirmDelete": "Hapus log sesi ini?", + "confirmDeleteDesc": "Tindakan ini tidak dapat dibatalkan.", + "copyContent": "Menyalin", + "copied": "Disalin!", + "loadError": "Gagal memuat log sesi", + "deleteError": "Gagal menghapus log sesi", + "filterByHost": "Saring berdasarkan host..." + } +} diff --git a/plugins/session-recording/locales/translated/it_IT.json b/plugins/session-recording/locales/translated/it_IT.json new file mode 100644 index 000000000..cc086bea5 --- /dev/null +++ b/plugins/session-recording/locales/translated/it_IT.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Log di sessione" + }, + "hosts": { + "enableSessionRecording": "Registrazione sessione", + "enableSessionRecordingDesc": "Registra l'output della sessione del terminale per la revisione successiva" + }, + "sessionLogs": { + "title": "Registri di sessione", + "noLogs": "Ancora nessun registro di sessione", + "noLogsDesc": "Abilita la registrazione delle sessioni su un host per iniziare a registrare", + "duration": "Durata", + "viewLog": "Visualizza registro", + "downloadLog": "Scarica", + "downloadAsText": "Scarica come testo", + "deleteLog": "Elimina", + "confirmDelete": "Eliminare questo registro di sessione?", + "confirmDeleteDesc": "Questa azione non può essere annullata.", + "copyContent": "Copia", + "copied": "Copiato!", + "loadError": "Impossibile caricare i registri di sessione", + "deleteError": "Impossibile eliminare il registro di sessione", + "filterByHost": "Filtra per host..." + } +} diff --git a/plugins/session-recording/locales/translated/ja_JP.json b/plugins/session-recording/locales/translated/ja_JP.json new file mode 100644 index 000000000..67486fdae --- /dev/null +++ b/plugins/session-recording/locales/translated/ja_JP.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "セッションログ" + }, + "hosts": { + "enableSessionRecording": "セッションログ", + "enableSessionRecordingDesc": "後で確認できるようにターミナルセッションの出力を記録します" + }, + "sessionLogs": { + "title": "セッションログ", + "noLogs": "セッションログはまだありません", + "noLogsDesc": "ホストでセッションログを有効にして記録を開始してください", + "duration": "所要時間", + "viewLog": "ログを表示", + "downloadLog": "ダウンロード", + "downloadAsText": "テキストとしてダウンロード", + "deleteLog": "削除", + "confirmDelete": "このセッションログを削除しますか?", + "confirmDeleteDesc": "この操作は元に戻せません。", + "copyContent": "コピー", + "copied": "コピーしました!", + "loadError": "セッションログの読み込みに失敗しました", + "deleteError": "セッションログの削除に失敗しました", + "filterByHost": "ホストでフィルター..." + } +} diff --git a/plugins/session-recording/locales/translated/ko_KR.json b/plugins/session-recording/locales/translated/ko_KR.json new file mode 100644 index 000000000..ac7c41b7b --- /dev/null +++ b/plugins/session-recording/locales/translated/ko_KR.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "세션 로그" + }, + "hosts": { + "enableSessionRecording": "세션 로깅", + "enableSessionRecordingDesc": "나중에 검토할 수 있도록 터미널 세션 출력을 기록합니다" + }, + "sessionLogs": { + "title": "세션 로그", + "noLogs": "아직 세션 로그 없음", + "noLogsDesc": "호스트에서 세션 로깅을 활성화하여 기록 시작", + "duration": "지속 시간", + "viewLog": "로그 보기", + "downloadLog": "다운로드", + "downloadAsText": "텍스트 파일로 다운로드", + "deleteLog": "삭제", + "confirmDelete": "이 세션 로그를 삭제하시겠습니까?", + "confirmDeleteDesc": "이 작업은 취소할 수 없습니다.", + "copyContent": "복사", + "copied": "복사됨!", + "loadError": "세션 로그 로드 실패", + "deleteError": "세션 로그 삭제 실패", + "filterByHost": "호스트로 필터링..." + } +} diff --git a/plugins/session-recording/locales/translated/nl_NL.json b/plugins/session-recording/locales/translated/nl_NL.json new file mode 100644 index 000000000..5c7cf6448 --- /dev/null +++ b/plugins/session-recording/locales/translated/nl_NL.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Sessielogboeken" + }, + "hosts": { + "enableSessionRecording": "Sessieregistratie", + "enableSessionRecordingDesc": "Sla de uitvoer van de terminalsessie op voor latere beoordeling." + }, + "sessionLogs": { + "title": "Sessielogboeken", + "noLogs": "Nog geen sessielogboeken", + "noLogsDesc": "Schakel sessielogging in op een host om te beginnen met opnemen.", + "duration": "Duur", + "viewLog": "Logboek bekijken", + "downloadLog": "Download", + "downloadAsText": "Downloaden als tekst", + "deleteLog": "Verwijderen", + "confirmDelete": "Dit sessielogboek verwijderen?", + "confirmDeleteDesc": "Deze actie kan niet ongedaan gemaakt worden.", + "copyContent": "Kopiëren", + "copied": "Gekopieerd!", + "loadError": "Het laden van de sessielogboeken is mislukt.", + "deleteError": "Het verwijderen van het sessielogboek is mislukt.", + "filterByHost": "Filteren op host..." + } +} diff --git a/plugins/session-recording/locales/translated/no_NO.json b/plugins/session-recording/locales/translated/no_NO.json new file mode 100644 index 000000000..b0cfa31f2 --- /dev/null +++ b/plugins/session-recording/locales/translated/no_NO.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Øktlogger" + }, + "hosts": { + "enableSessionRecording": "Øktlogging", + "enableSessionRecordingDesc": "Ta opp utdata fra terminaløkten for senere gjennomgang" + }, + "sessionLogs": { + "title": "Øktlogger", + "noLogs": "Ingen øktlogger ennå", + "noLogsDesc": "Aktiver øktlogging på en vert for å starte opptaket", + "duration": "Varighet", + "viewLog": "Vis logg", + "downloadLog": "Last ned", + "downloadAsText": "Last ned som tekst", + "deleteLog": "Slett", + "confirmDelete": "Slette denne øktloggen?", + "confirmDeleteDesc": "Denne handlingen kan ikke angres.", + "copyContent": "Kopiere", + "copied": "Kopiert!", + "loadError": "Kunne ikke laste inn øktlogger", + "deleteError": "Kunne ikke slette øktloggen", + "filterByHost": "Filtrer etter vert..." + } +} diff --git a/plugins/session-recording/locales/translated/pl_PL.json b/plugins/session-recording/locales/translated/pl_PL.json new file mode 100644 index 000000000..dfc129577 --- /dev/null +++ b/plugins/session-recording/locales/translated/pl_PL.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Logi sesji" + }, + "hosts": { + "enableSessionRecording": "Rejestrowanie sesji", + "enableSessionRecordingDesc": "Zapisuj wyjście sesji terminala do późniejszego przejrzenia" + }, + "sessionLogs": { + "title": "Logi sesji", + "noLogs": "Brak logów sesji", + "noLogsDesc": "Włącz rejestrowanie sesji na hoście, aby rozpocząć nagrywanie", + "duration": "Czas trwania", + "viewLog": "Wyświetl log", + "downloadLog": "Pobierz", + "downloadAsText": "Pobierz jako tekst", + "deleteLog": "Usuń", + "confirmDelete": "Usunąć ten log sesji?", + "confirmDeleteDesc": "Tej czynności nie można cofnąć.", + "copyContent": "Kopiuj", + "copied": "Skopiowano!", + "loadError": "Nie udało się załadować logów sesji", + "deleteError": "Nie udało się usunąć logu sesji", + "filterByHost": "Filtruj według hosta..." + } +} diff --git a/plugins/session-recording/locales/translated/pt_BR.json b/plugins/session-recording/locales/translated/pt_BR.json new file mode 100644 index 000000000..4e532cc88 --- /dev/null +++ b/plugins/session-recording/locales/translated/pt_BR.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Registros de sessão" + }, + "hosts": { + "enableSessionRecording": "Registro de sessão", + "enableSessionRecordingDesc": "Registre a saída da sessão do terminal para revisão posterior." + }, + "sessionLogs": { + "title": "Registros de sessão", + "noLogs": "Ainda não há registros de sessão.", + "noLogsDesc": "Habilite o registro de sessão em um host para iniciar a gravação.", + "duration": "Duração", + "viewLog": "Ver registro", + "downloadLog": "Download", + "downloadAsText": "Baixar como texto", + "deleteLog": "Excluir", + "confirmDelete": "Excluir este registro de sessão?", + "confirmDeleteDesc": "Esta ação não pode ser desfeita.", + "copyContent": "Cópia", + "copied": "Copiado!", + "loadError": "Falha ao carregar os registros da sessão.", + "deleteError": "Falha ao excluir o registro da sessão", + "filterByHost": "Filtrar por host..." + } +} diff --git a/plugins/session-recording/locales/translated/pt_PT.json b/plugins/session-recording/locales/translated/pt_PT.json new file mode 100644 index 000000000..7de107f35 --- /dev/null +++ b/plugins/session-recording/locales/translated/pt_PT.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Registos de Sessão" + }, + "hosts": { + "enableSessionRecording": "Registo de sessão", + "enableSessionRecordingDesc": "Gravar a saída da sessão de terminal para revisão posterior" + }, + "sessionLogs": { + "title": "Registos de Sessão", + "noLogs": "Ainda sem registos de sessão", + "noLogsDesc": "Ative o registo de sessão num host para começar a gravar", + "duration": "Duração", + "viewLog": "Ver registo", + "downloadLog": "Transferir", + "downloadAsText": "Descarregar como texto", + "deleteLog": "Eliminar", + "confirmDelete": "Eliminar este registo de sessão?", + "confirmDeleteDesc": "Esta ação não pode ser desfeita.", + "copyContent": "Copiar", + "copied": "Copiado!", + "loadError": "Falha ao carregar registos de sessão", + "deleteError": "Falha ao eliminar registo de sessão", + "filterByHost": "Filtrar por host..." + } +} diff --git a/plugins/session-recording/locales/translated/ro_RO.json b/plugins/session-recording/locales/translated/ro_RO.json new file mode 100644 index 000000000..c1ee6fd96 --- /dev/null +++ b/plugins/session-recording/locales/translated/ro_RO.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Jurnalele de sesiune" + }, + "hosts": { + "enableSessionRecording": "Înregistrarea sesiunilor", + "enableSessionRecordingDesc": "Înregistrați ieșirea sesiunii terminale pentru o revizuire ulterioară" + }, + "sessionLogs": { + "title": "Jurnalele de sesiune", + "noLogs": "Încă nu există jurnale de sesiune", + "noLogsDesc": "Activați înregistrarea în jurnal pe o gazdă pentru a începe înregistrarea", + "duration": "Durată", + "viewLog": "Vizualizare jurnal", + "downloadLog": "Descărcare", + "downloadAsText": "Descărcați ca text", + "deleteLog": "Şterge", + "confirmDelete": "Ștergeți acest jurnal de sesiune?", + "confirmDeleteDesc": "Această acțiune nu poate fi anulată.", + "copyContent": "Copie", + "copied": "Copiat!", + "loadError": "Nu s-au putut încărca jurnalele de sesiune", + "deleteError": "Nu s-a putut șterge jurnalul sesiunii", + "filterByHost": "Filtrare după gazdă..." + } +} diff --git a/plugins/session-recording/locales/translated/ru_RU.json b/plugins/session-recording/locales/translated/ru_RU.json new file mode 100644 index 000000000..92215207c --- /dev/null +++ b/plugins/session-recording/locales/translated/ru_RU.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Журналы сессий" + }, + "hosts": { + "enableSessionRecording": "Логирование сессии", + "enableSessionRecordingDesc": "Записывать вывод терминальной сессии для последующего просмотра" + }, + "sessionLogs": { + "title": "Журналы сеансов", + "noLogs": "Пока нет журналов сеансов", + "noLogsDesc": "Включите ведение журнала сеансов на хосте, чтобы начать запись", + "duration": "Продолжительность", + "viewLog": "Просмотреть журнал", + "downloadLog": "Скачать", + "downloadAsText": "Скачать как текст", + "deleteLog": "Удалить", + "confirmDelete": "Удалить этот журнал сеансов?", + "confirmDeleteDesc": "Это действие нельзя отменить.", + "copyContent": "Копировать", + "copied": "Скопировано!", + "loadError": "Не удалось загрузить журналы сеансов", + "deleteError": "Не удалось удалить журнал сеанса", + "filterByHost": "Фильтровать по хосту..." + } +} diff --git a/plugins/session-recording/locales/translated/sr_SP.json b/plugins/session-recording/locales/translated/sr_SP.json new file mode 100644 index 000000000..9efe884a6 --- /dev/null +++ b/plugins/session-recording/locales/translated/sr_SP.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Записи сесија" + }, + "hosts": { + "enableSessionRecording": "Записивање сесије", + "enableSessionRecordingDesc": "Снимите излаз терминалне сесије за каснији преглед" + }, + "sessionLogs": { + "title": "Записи сесија", + "noLogs": "Још нема евиденција сесија", + "noLogsDesc": "Омогућите евидентирање сесије на хосту да бисте започели снимање", + "duration": "Трајање", + "viewLog": "Прикажи дневник", + "downloadLog": "Преузми", + "downloadAsText": "Преузми као текст", + "deleteLog": "Обриши", + "confirmDelete": "Да ли желите да обришете овај дневник сесије?", + "confirmDeleteDesc": "Ова радња се не може поништити.", + "copyContent": "Копија", + "copied": "Копирано!", + "loadError": "Учитавање евиденције сесије није успело", + "deleteError": "Брисање дневника сесије није успело", + "filterByHost": "Филтрирај по хосту..." + } +} diff --git a/plugins/session-recording/locales/translated/sv_SE.json b/plugins/session-recording/locales/translated/sv_SE.json new file mode 100644 index 000000000..46bf42ed1 --- /dev/null +++ b/plugins/session-recording/locales/translated/sv_SE.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Sessionsloggar" + }, + "hosts": { + "enableSessionRecording": "Sessionsloggning", + "enableSessionRecordingDesc": "Spela in utdata från terminalsessionen för senare granskning" + }, + "sessionLogs": { + "title": "Sessionsloggar", + "noLogs": "Inga sessionsloggar ännu", + "noLogsDesc": "Aktivera sessionsloggning på en värd för att starta inspelning", + "duration": "Varaktighet", + "viewLog": "Visa logg", + "downloadLog": "Ladda ner", + "downloadAsText": "Ladda ner som text", + "deleteLog": "Radera", + "confirmDelete": "Radera den här sessionsloggen?", + "confirmDeleteDesc": "Den här åtgärden kan inte ångras.", + "copyContent": "Kopiera", + "copied": "Kopierad!", + "loadError": "Misslyckades med att läsa in sessionsloggar", + "deleteError": "Misslyckades med att radera sessionsloggen", + "filterByHost": "Filtrera efter värd..." + } +} diff --git a/plugins/session-recording/locales/translated/th_TH.json b/plugins/session-recording/locales/translated/th_TH.json new file mode 100644 index 000000000..5d485f559 --- /dev/null +++ b/plugins/session-recording/locales/translated/th_TH.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "บันทึกเซสชัน" + }, + "hosts": { + "enableSessionRecording": "การบันทึกเซสชัน", + "enableSessionRecordingDesc": "บันทึกผลลัพธ์จากเทอร์มินัลเพื่อตรวจสอบในภายหลัง" + }, + "sessionLogs": { + "title": "บันทึกเซสชัน", + "noLogs": "ยังไม่มีบันทึกการใช้งานใดๆ", + "noLogsDesc": "เปิดใช้งานการบันทึกเซสชันบนโฮสต์เพื่อเริ่มการบันทึก", + "duration": "ระยะเวลา", + "viewLog": "ดูบันทึก", + "downloadLog": "ดาวน์โหลด", + "downloadAsText": "ดาวน์โหลดเป็นข้อความ", + "deleteLog": "ลบ", + "confirmDelete": "ลบบันทึกการใช้งานนี้หรือไม่?", + "confirmDeleteDesc": "การกระทำนี้ไม่สามารถย้อนกลับได้", + "copyContent": "สำเนา", + "copied": "คัดลอกแล้ว!", + "loadError": "ไม่สามารถโหลดบันทึกเซสชันได้", + "deleteError": "ไม่สามารถลบบันทึกเซสชันได้", + "filterByHost": "กรองตามโฮสต์..." + } +} diff --git a/plugins/session-recording/locales/translated/tr_TR.json b/plugins/session-recording/locales/translated/tr_TR.json new file mode 100644 index 000000000..0ffe8f181 --- /dev/null +++ b/plugins/session-recording/locales/translated/tr_TR.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Oturum Kayıtları" + }, + "hosts": { + "enableSessionRecording": "Oturum Kaydı", + "enableSessionRecordingDesc": "Daha sonra gözden geçirmek için terminal oturumu çıktısını kaydet" + }, + "sessionLogs": { + "title": "Oturum Günlükleri", + "noLogs": "Henüz oturum günlüğü yok", + "noLogsDesc": "Kayda başlamak için bir sunucuda oturum günlüğünü etkinleştirin", + "duration": "Süre", + "viewLog": "Günlüğü görüntüle", + "downloadLog": "İndir", + "downloadAsText": "Metin olarak indir", + "deleteLog": "Sil", + "confirmDelete": "Bu oturum günlüğü silinsin mi?", + "confirmDeleteDesc": "Bu işlem geri alınamaz.", + "copyContent": "Kopyala", + "copied": "Kopyalandı!", + "loadError": "Oturum günlükleri yüklenemedi", + "deleteError": "Oturum günlüğü silinemedi", + "filterByHost": "Sunucuya göre filtrele..." + } +} diff --git a/plugins/session-recording/locales/translated/uk_UA.json b/plugins/session-recording/locales/translated/uk_UA.json new file mode 100644 index 000000000..d6192bdeb --- /dev/null +++ b/plugins/session-recording/locales/translated/uk_UA.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Журнали сеансів" + }, + "hosts": { + "enableSessionRecording": "Ведення журналу сеансу", + "enableSessionRecordingDesc": "Запис виводу сеансу терміналу для подальшого перегляду" + }, + "sessionLogs": { + "title": "Журнали сеансів", + "noLogs": "Журналів сеансів поки що немає", + "noLogsDesc": "Увімкнути ведення журналу сеансу на хості, щоб розпочати запис", + "duration": "Тривалість", + "viewLog": "Переглянути журнал", + "downloadLog": "Завантажити", + "downloadAsText": "Завантажити як текст", + "deleteLog": "Видалити", + "confirmDelete": "Видалити цей журнал сеансу?", + "confirmDeleteDesc": "Цю дію не можна скасувати.", + "copyContent": "Копіювати", + "copied": "Скопійовано!", + "loadError": "Не вдалося завантажити журнали сеансу", + "deleteError": "Не вдалося видалити журнал сеансу", + "filterByHost": "Фільтрувати за хостом..." + } +} diff --git a/plugins/session-recording/locales/translated/vi_VN.json b/plugins/session-recording/locales/translated/vi_VN.json new file mode 100644 index 000000000..08ae5af9e --- /dev/null +++ b/plugins/session-recording/locales/translated/vi_VN.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "Nhật ký phiên" + }, + "hosts": { + "enableSessionRecording": "Ghi nhật ký phiên", + "enableSessionRecordingDesc": "Ghi lại đầu ra phiên terminal để xem lại sau" + }, + "sessionLogs": { + "title": "Nhật ký phiên", + "noLogs": "Chưa có nhật ký phiên nào", + "noLogsDesc": "Bật ghi nhật ký phiên trên máy chủ để bắt đầu ghi", + "duration": "Thời lượng", + "viewLog": "Xem nhật ký", + "downloadLog": "Tải xuống", + "downloadAsText": "Tải xuống dưới dạng văn bản", + "deleteLog": "Xóa", + "confirmDelete": "Xóa nhật ký phiên này?", + "confirmDeleteDesc": "Hành động này không thể hoàn tác.", + "copyContent": "Sao chép", + "copied": "Đã sao chép!", + "loadError": "Tải nhật ký phiên thất bại", + "deleteError": "Xóa nhật ký phiên thất bại", + "filterByHost": "Lọc theo máy chủ..." + } +} diff --git a/plugins/session-recording/locales/translated/zh_CN.json b/plugins/session-recording/locales/translated/zh_CN.json new file mode 100644 index 000000000..4ad11678a --- /dev/null +++ b/plugins/session-recording/locales/translated/zh_CN.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "会话日志" + }, + "hosts": { + "enableSessionRecording": "会话日志记录", + "enableSessionRecordingDesc": "记录终端会话输出以供日后查看" + }, + "sessionLogs": { + "title": "会话日志", + "noLogs": "暂无会话日志", + "noLogsDesc": "在主机上启用会话日志以开始记录", + "duration": "持续时间", + "viewLog": "查看日志", + "downloadLog": "下载", + "downloadAsText": "下载为文本", + "deleteLog": "删除", + "confirmDelete": "删除此会话日志?", + "confirmDeleteDesc": "此操作无法撤消。", + "copyContent": "复制", + "copied": "已复制!", + "loadError": "加载会话日志失败", + "deleteError": "删除会话日志失败", + "filterByHost": "按主机筛选..." + } +} diff --git a/plugins/session-recording/locales/translated/zh_TW.json b/plugins/session-recording/locales/translated/zh_TW.json new file mode 100644 index 000000000..4a8808a64 --- /dev/null +++ b/plugins/session-recording/locales/translated/zh_TW.json @@ -0,0 +1,26 @@ +{ + "nav": { + "sessionLogs": "工作階段記錄" + }, + "hosts": { + "enableSessionRecording": "工作階段記錄", + "enableSessionRecordingDesc": "記錄終端機工作階段輸出以供日後檢視" + }, + "sessionLogs": { + "title": "工作階段記錄", + "noLogs": "尚無任何工作階段記錄", + "noLogsDesc": "在主機上啟用工作階段記錄以開始錄製", + "duration": "持續時間", + "viewLog": "檢視記錄", + "downloadLog": "下載", + "downloadAsText": "下載為文字", + "deleteLog": "刪除", + "confirmDelete": "刪除此工作階段記錄?", + "confirmDeleteDesc": "此動作無法復原。", + "copyContent": "複製", + "copied": "已複製!", + "loadError": "載入工作階段記錄失敗", + "deleteError": "刪除工作階段記錄失敗", + "filterByHost": "依主機篩選..." + } +} diff --git a/plugins/session-recording/manifest.json b/plugins/session-recording/manifest.json new file mode 100644 index 000000000..17ba92920 --- /dev/null +++ b/plugins/session-recording/manifest.json @@ -0,0 +1,74 @@ +{ + "id": "session-recording", + "name": "Session Recording", + "version": "1.0.0", + "description": "Records terminal sessions and lets you play them back or download them.", + "author": { + "name": "Termix" + }, + "license": "MIT", + "repository": "https://github.com/Termix-SSH/Termix", + "category": "Terminal", + "icon": "ScrollText", + "engine": { + "termix": ">=2.9.0", + "api": "1" + }, + "capabilities": [ + "hosts:read", + "db:own", + "files:own", + "network:serve", + "ui:surface" + ], + "provides": [ + { + "service": "recordings.writer", + "version": "1.0.0", + "permission": "session-recording.view" + } + ], + "contributes": { + "tabs": [ + { + "id": "session-logs", + "titleKey": "nav.sessionLogs", + "icon": "ScrollText", + "openFrom": ["rail"] + } + ], + "permissions": [ + { + "name": "view", + "titleKey": "permissions.view.title", + "descriptionKey": "permissions.view.description", + "defaultRoles": ["admin", "user"] + } + ], + "settings": { + "admin": [ + { + "key": "retentionDays", + "type": "number", + "labelKey": "settings.admin.retentionDays.label", + "descriptionKey": "settings.admin.retentionDays.description", + "min": 1, + "max": 3650, + "default": 30 + } + ], + "host": { + "enableKey": "enableSessionRecording", + "enableLabelKey": "settings.host.enable.label", + "fields": [] + } + }, + "hostCapability": { + "key": "enableSessionRecording", + "labelKey": "hosts.enableSessionRecording", + "editorTab": "general" + } + }, + "locales": "locales", + "platforms": ["linux", "win32", "darwin"] +} diff --git a/plugins/session-recording/migrations/mysql/0001_adopt_session_recordings.sql b/plugins/session-recording/migrations/mysql/0001_adopt_session_recordings.sql new file mode 100644 index 000000000..b0760ddf7 --- /dev/null +++ b/plugins/session-recording/migrations/mysql/0001_adopt_session_recordings.sql @@ -0,0 +1,22 @@ +-- session-recording 0001: adopt_session_recordings +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS `session_recordings` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `host_id` int NOT NULL, + `user_id` varchar(255), + `username` text, + `access_id` int, + `started_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP), + `ended_at` text, + `duration` int, + `commands` text, + `dangerous_actions` text, + `recording_path` text, + `protocol` text NOT NULL DEFAULT ('ssh'), + `format` text NOT NULL DEFAULT ('text'), + `terminated_by_owner` boolean DEFAULT false, + `termination_reason` text, + FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE +); +RENAME TABLE `session_recordings` TO `p_session_recording_session_recordings`; diff --git a/plugins/session-recording/migrations/postgres/0001_adopt_session_recordings.sql b/plugins/session-recording/migrations/postgres/0001_adopt_session_recordings.sql new file mode 100644 index 000000000..4cd436667 --- /dev/null +++ b/plugins/session-recording/migrations/postgres/0001_adopt_session_recordings.sql @@ -0,0 +1,24 @@ +-- session-recording 0001: adopt_session_recordings +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "session_recordings" ( + "id" serial PRIMARY KEY, + "host_id" integer NOT NULL, + "user_id" varchar(255), + "username" text, + "access_id" integer, + "started_at" varchar(255) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ended_at" text, + "duration" integer, + "commands" text, + "dangerous_actions" text, + "recording_path" text, + "protocol" text NOT NULL DEFAULT 'ssh', + "format" text NOT NULL DEFAULT 'text', + "terminated_by_owner" boolean DEFAULT false, + "termination_reason" text, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_session_recordings_user_started" ON "session_recordings" ("user_id", "started_at"); +CREATE INDEX IF NOT EXISTS "idx_session_recordings_host" ON "session_recordings" ("host_id"); +ALTER TABLE "session_recordings" RENAME TO "p_session_recording_session_recordings"; diff --git a/plugins/session-recording/migrations/snapshot.json b/plugins/session-recording/migrations/snapshot.json new file mode 100644 index 000000000..add1a0836 --- /dev/null +++ b/plugins/session-recording/migrations/snapshot.json @@ -0,0 +1,76 @@ +{ + "version": 1, + "tables": { + "session_recordings": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "hostId": { + "type": "refHost", + "notNull": true + }, + "userId": { + "type": "varchar", + "length": 255 + }, + "username": { + "type": "text" + }, + "accessId": { + "type": "integer" + }, + "startedAt": { + "type": "timestamp", + "notNull": true, + "defaultNow": true + }, + "endedAt": { + "type": "timestamp" + }, + "duration": { + "type": "integer" + }, + "commands": { + "type": "text" + }, + "dangerousActions": { + "type": "text" + }, + "recordingPath": { + "type": "text" + }, + "protocol": { + "type": "text", + "notNull": true, + "defaultValue": "ssh" + }, + "format": { + "type": "text", + "notNull": true, + "defaultValue": "text" + }, + "terminatedByOwner": { + "type": "boolean", + "defaultValue": false + }, + "terminationReason": { + "type": "text" + } + }, + "indexes": [ + { + "name": "idx_session_recordings_user_started", + "columns": ["userId", "startedAt"] + }, + { + "name": "idx_session_recordings_host", + "columns": ["hostId"] + } + ], + "adopts": "session_recordings" + } + } +} diff --git a/plugins/session-recording/migrations/sqlite/0001_adopt_session_recordings.sql b/plugins/session-recording/migrations/sqlite/0001_adopt_session_recordings.sql new file mode 100644 index 000000000..93f9ba02f --- /dev/null +++ b/plugins/session-recording/migrations/sqlite/0001_adopt_session_recordings.sql @@ -0,0 +1,24 @@ +-- session-recording 0001: adopt_session_recordings +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "session_recordings" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "host_id" integer NOT NULL, + "user_id" text, + "username" text, + "access_id" integer, + "started_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ended_at" text, + "duration" integer, + "commands" text, + "dangerous_actions" text, + "recording_path" text, + "protocol" text NOT NULL DEFAULT 'ssh', + "format" text NOT NULL DEFAULT 'text', + "terminated_by_owner" integer DEFAULT 0, + "termination_reason" text, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_session_recordings_user_started" ON "session_recordings" ("user_id", "started_at"); +CREATE INDEX IF NOT EXISTS "idx_session_recordings_host" ON "session_recordings" ("host_id"); +ALTER TABLE "session_recordings" RENAME TO "p_session_recording_session_recordings"; diff --git a/plugins/session-recording/package.json b/plugins/session-recording/package.json new file mode 100644 index 000000000..7151dd6c5 --- /dev/null +++ b/plugins/session-recording/package.json @@ -0,0 +1,15 @@ +{ + "name": "@termix-plugin/session-recording", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "build": "termix-plugin build", + "test": "termix-plugin test", + "typecheck": "tsc -p tsconfig.json", + "validate": "termix-plugin validate" + }, + "devDependencies": { + "@termix/plugin-sdk": "*" + } +} diff --git a/plugins/session-recording/src/backend/index.ts b/plugins/session-recording/src/backend/index.ts new file mode 100644 index 000000000..8df126870 --- /dev/null +++ b/plugins/session-recording/src/backend/index.ts @@ -0,0 +1,42 @@ +import type { Router } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { sessionRecordings } from "./tables.js"; +import { createSessionRecordingRepository } from "./repository.js"; +import { createRecordingsWriter, type RecordingsWriterV1 } from "./service.js"; +import { registerSessionRecordingRoutes } from "./routes.js"; +import { startRetentionSweep } from "./retention.js"; + +export type { RecordingsWriterV1, RecordingSink } from "./service.js"; + +export async function activate(ctx: PluginContext) { + const table = await ctx.db.define(sessionRecordings); + const repository = createSessionRecordingRepository(ctx.db, table, ctx.hosts); + + ctx.http + .router() + .use( + registerSessionRecordingRoutes(ctx, repository, () => + ctx.files.dataDir(), + ), + ); + + ctx.services.provide( + "recordings.writer", + createRecordingsWriter(ctx, repository), + ); + + // A recording is evidence about the host as much as the person, so it + // outlives the account: the row's userId is cleared rather than cascading. + ctx.events.on("user.deleted", (payload) => { + const { userId } = payload as { userId: string }; + void repository.anonymizeByUserId(userId); + }); + + startRetentionSweep(ctx, repository, () => ctx.files.dataDir()); + + ctx.log.info( + "Session Recording routes mounted at /plugin-api/session-recording", + ); +} + +export async function deactivate() {} diff --git a/plugins/session-recording/src/backend/repository.ts b/plugins/session-recording/src/backend/repository.ts new file mode 100644 index 000000000..7a3779f68 --- /dev/null +++ b/plugins/session-recording/src/backend/repository.ts @@ -0,0 +1,240 @@ +import { and, desc, eq, inArray, lt } from "drizzle-orm"; +import type { PluginDatabase, PluginHosts } from "@termix/plugin-sdk/backend"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +// The table comes from ctx.db.define, which the SDK hands back untyped, and +// the drizzle handle is the server's own. Typed at this module's edge instead. +type Table = any; +type Drizzle = any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +export interface SessionRecordingRecord { + id: number; + hostId: number; + userId: string | null; + username: string | null; + startedAt: string; + endedAt: string | null; + duration: number | null; + recordingPath: string | null; + protocol: string; + format: string | null; + terminatedByOwner: boolean | null; + terminationReason: string | null; +} + +export interface SessionRecordingListRecord { + id: number; + hostId: number; + userId: string | null; + startedAt: string; + endedAt: string | null; + duration: number | null; + recordingPath: string | null; + protocol: string; + format: string | null; + username: string | null; + hostName: string | null; + hostIp: string | null; +} + +export interface SessionRecordingCreateInput { + hostId: number; + userId: string; + username?: string | null; + startedAt: string; + endedAt?: string | null; + duration?: number | null; + recordingPath?: string | null; + protocol?: string | null; + format?: string | null; + terminatedByOwner?: boolean | null; + terminationReason?: string | null; +} + +export type SessionRecordingRepository = ReturnType< + typeof createSessionRecordingRepository +>; + +/** + * Written without RETURNING so it runs the same on all three engines: an + * insert is read back by a marker key it was given, an update by its id. + */ +export function createSessionRecordingRepository( + db: PluginDatabase, + table: Table, + hosts: PluginHosts, +) { + const client = () => db.client(); + + async function usernameFor(userId: string): Promise { + const { users } = await db.refs<{ users: Table }>(); + const drizzle = await client(); + const rows = await drizzle + .select({ username: users.username }) + .from(users) + .where(eq(users.id, userId)); + return rows[0]?.username ?? null; + } + + async function withHostNames( + rows: SessionRecordingListRecord[], + ): Promise { + const list = await hosts.list(); + const byId = new Map(list.map((host) => [host.id, host])); + return rows.map((row) => { + const host = byId.get(row.hostId); + return { ...row, hostName: host?.name ?? null, hostIp: host?.ip ?? null }; + }); + } + + return { + usernameFor, + + /** + * Inserts a row and returns it. recordingPath is the caller's read-back + * key: every caller sets it to something unique (the file path or a + * session id) before insert, since MySQL has no RETURNING to read a new + * id back with. + */ + async create( + input: SessionRecordingCreateInput & { recordingPath: string }, + ): Promise { + const drizzle = await client(); + await drizzle.insert(table).values(input); + const rows = await drizzle + .select() + .from(table) + .where(eq(table.recordingPath, input.recordingPath)) + .orderBy(desc(table.id)) + .limit(1); + await db.persist(); + return rows[0]; + }, + + async updateEnded( + id: number, + input: { + endedAt: string; + duration: number | null; + terminatedByOwner?: boolean; + terminationReason?: string | null; + }, + ): Promise { + const drizzle = await client(); + await drizzle.update(table).set(input).where(eq(table.id, id)); + await db.persist(); + }, + + async findByIdForUser( + userId: string, + id: number, + ): Promise { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(table) + .where(and(eq(table.id, id), eq(table.userId, userId))) + .limit(1); + return rows[0] ?? null; + }, + + async findPathByIdForUser( + userId: string, + id: number, + ): Promise<{ + id: number; + recordingPath: string | null; + format: string | null; + } | null> { + const drizzle = await client(); + const rows = await drizzle + .select({ + id: table.id, + recordingPath: table.recordingPath, + format: table.format, + }) + .from(table) + .where(and(eq(table.id, id), eq(table.userId, userId))) + .limit(1); + return rows[0] ?? null; + }, + + async listByUserIdWithHost( + userId: string, + ): Promise { + const drizzle = await client(); + const rows = (await drizzle + .select() + .from(table) + .where(eq(table.userId, userId)) + .orderBy(desc(table.startedAt))) as SessionRecordingListRecord[]; + return withHostNames(rows); + }, + + async listPathsOlderThan( + cutoff: string, + ): Promise> { + const drizzle = await client(); + return drizzle + .select({ id: table.id, recordingPath: table.recordingPath }) + .from(table) + .where(lt(table.startedAt, cutoff)); + }, + + async deleteForUser(userId: string, id: number): Promise { + const drizzle = await client(); + const existing = await drizzle + .select({ id: table.id }) + .from(table) + .where(and(eq(table.id, id), eq(table.userId, userId))) + .limit(1); + if (existing.length === 0) return false; + await drizzle + .delete(table) + .where(and(eq(table.id, id), eq(table.userId, userId))); + await db.persist(); + return true; + }, + + async deleteById(id: number): Promise { + const drizzle = await client(); + const existing = await drizzle + .select({ id: table.id }) + .from(table) + .where(eq(table.id, id)) + .limit(1); + if (existing.length === 0) return false; + await drizzle.delete(table).where(eq(table.id, id)); + await db.persist(); + return true; + }, + + async deleteByHostId(hostId: number): Promise { + const drizzle = await client(); + await drizzle.delete(table).where(eq(table.hostId, hostId)); + await db.persist(); + }, + + async deleteByHostIds(hostIds: number[]): Promise { + if (hostIds.length === 0) return; + const drizzle = await client(); + await drizzle.delete(table).where(inArray(table.hostId, hostIds)); + await db.persist(); + }, + + /** + * Detaches recordings from a user being deleted instead of removing them. + * A recording is evidence about the host as much as about the person, and + * the file stays on disk regardless of the row. + */ + async anonymizeByUserId(userId: string): Promise { + const drizzle = await client(); + await drizzle + .update(table) + .set({ userId: null }) + .where(eq(table.userId, userId)); + await db.persist(); + }, + }; +} diff --git a/plugins/session-recording/src/backend/retention.ts b/plugins/session-recording/src/backend/retention.ts new file mode 100644 index 000000000..df10b49f7 --- /dev/null +++ b/plugins/session-recording/src/backend/retention.ts @@ -0,0 +1,64 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { SessionRecordingRepository } from "./repository.js"; + +const DEFAULT_RETENTION_DAYS = 30; +const PRUNE_INTERVAL_MS = 24 * 60 * 60 * 1000; + +async function getRetentionDays(ctx: PluginContext): Promise { + const configured = await ctx.settings.get("retentionDays"); + if (typeof configured === "number" && configured >= 1 && configured <= 3650) { + return configured; + } + return DEFAULT_RETENTION_DAYS; +} + +async function pruneOldRecordings( + ctx: PluginContext, + repository: SessionRecordingRepository, + dataDir: () => Promise, +): Promise { + try { + const retentionDays = await getRetentionDays(ctx); + const cutoff = new Date( + Date.now() - retentionDays * 24 * 60 * 60 * 1000, + ).toISOString(); + const old = await repository.listPathsOlderThan(cutoff); + if (old.length === 0) return; + + const base = `${path.resolve(await dataDir())}${path.sep}`; + for (const row of old) { + if (row.recordingPath) { + const resolved = path.resolve(row.recordingPath); + if (resolved.startsWith(base)) { + await fs.unlink(resolved).catch(() => {}); + } + } + await repository.deleteById(row.id); + } + + ctx.log.info(`Pruned ${old.length} old session recording(s)`); + } catch (error) { + ctx.log.warn( + `Failed to prune old session recordings: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } +} + +/** Runs the retention sweep at startup and every 24 hours. */ +export function startRetentionSweep( + ctx: PluginContext, + repository: SessionRecordingRepository, + dataDir: () => Promise, +): void { + void pruneOldRecordings(ctx, repository, dataDir); + const timer = setInterval( + () => void pruneOldRecordings(ctx, repository, dataDir), + PRUNE_INTERVAL_MS, + ); + timer.unref?.(); + ctx.disposables.add(() => clearInterval(timer)); +} diff --git a/plugins/session-recording/src/backend/routes.ts b/plugins/session-recording/src/backend/routes.ts new file mode 100644 index 000000000..705ec4c0b --- /dev/null +++ b/plugins/session-recording/src/backend/routes.ts @@ -0,0 +1,253 @@ +import fs from "node:fs"; +import path from "node:path"; +import express, { type Request, type Response, type Router } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { SessionRecordingRepository } from "./repository.js"; + +function actorId(ctx: PluginContext): string | undefined { + return ctx.currentActor(); +} + +/** + * @openapi + * /plugin-api/session-recording/: + * get: + * summary: List session recordings + * description: Returns every terminal session recording for the authenticated user. + * tags: + * - Session Recording + * responses: + * 200: + * description: List of session recordings. + * 500: + * description: Failed to fetch session recordings. + */ +function registerList( + router: Router, + ctx: PluginContext, + repository: SessionRecordingRepository, +): void { + router.get("/", async (_req: Request, res: Response) => { + const userId = actorId(ctx); + if (!userId) return res.status(401).json({ error: "Not authenticated" }); + try { + const rows = await repository.listByUserIdWithHost(userId); + const records = rows.map((row) => { + let sizeBytes: number | null = null; + if (row.recordingPath) { + try { + sizeBytes = fs.statSync(row.recordingPath).size; + } catch { + // file may have been removed + } + } + return { ...row, sizeBytes }; + }); + res.json({ logs: records }); + } catch (error) { + ctx.log.error( + "Failed to fetch session recordings", + error instanceof Error ? error : new Error(String(error)), + ); + res.status(500).json({ error: "Failed to fetch session recordings" }); + } + }); +} + +/** + * @openapi + * /plugin-api/session-recording/{id}: + * get: + * summary: Get session recording metadata + * description: Returns metadata for a single session recording. + * tags: + * - Session Recording + * parameters: + * - in: path + * name: id + * required: true + * schema: { type: integer } + * responses: + * 200: + * description: Session recording metadata. + * 404: + * description: Session recording not found. + * 500: + * description: Failed to fetch session recording. + */ +function registerGet( + router: Router, + ctx: PluginContext, + repository: SessionRecordingRepository, +): void { + router.get("/:id", async (req: Request, res: Response) => { + const userId = actorId(ctx); + if (!userId) return res.status(401).json({ error: "Not authenticated" }); + const rawId = Array.isArray(req.params.id) + ? req.params.id[0] + : req.params.id; + const id = parseInt(rawId, 10); + if (isNaN(id)) return res.status(400).json({ error: "Invalid id" }); + + try { + const row = await repository.findByIdForUser(userId, id); + if (!row) return res.status(404).json({ error: "Not found" }); + res.json({ log: row }); + } catch (error) { + ctx.log.error( + "Failed to fetch session recording", + error instanceof Error ? error : new Error(String(error)), + ); + res.status(500).json({ error: "Failed to fetch session recording" }); + } + }); +} + +/** + * @openapi + * /plugin-api/session-recording/{id}/content: + * get: + * summary: Get session recording content + * description: Returns the raw content of a session recording file. + * tags: + * - Session Recording + * parameters: + * - in: path + * name: id + * required: true + * schema: { type: integer } + * responses: + * 200: + * description: Raw recording content. + * 404: + * description: Session recording or file not found. + * 500: + * description: Failed to read session recording. + */ +function registerContent( + router: Router, + ctx: PluginContext, + repository: SessionRecordingRepository, + dataDir: () => Promise, +): void { + router.get("/:id/content", async (req: Request, res: Response) => { + const userId = actorId(ctx); + if (!userId) return res.status(401).json({ error: "Not authenticated" }); + const rawId = Array.isArray(req.params.id) + ? req.params.id[0] + : req.params.id; + const id = parseInt(rawId, 10); + if (isNaN(id)) return res.status(400).json({ error: "Invalid id" }); + + try { + const row = await repository.findPathByIdForUser(userId, id); + if (!row) return res.status(404).json({ error: "Not found" }); + + const filePath = row.recordingPath; + if (!filePath) + return res.status(404).json({ error: "No recording file" }); + + const resolvedPath = path.resolve(filePath); + const base = `${path.resolve(await dataDir())}${path.sep}`; + if (!resolvedPath.startsWith(base)) { + return res.status(403).json({ error: "Forbidden" }); + } + if (!fs.existsSync(resolvedPath)) { + return res.status(404).json({ error: "File not found" }); + } + + const content = await fs.promises.readFile(resolvedPath); + const format = + row.format ?? (filePath.endsWith(".cast") ? "asciicast" : "text"); + const contentType = + format === "guacamole" + ? "application/vnd.apache.guacamole.recording" + : format === "asciicast" + ? "application/x-asciicast" + : "text/plain"; + res.type(contentType).send(content); + } catch (error) { + ctx.log.error( + "Failed to read session recording content", + error instanceof Error ? error : new Error(String(error)), + ); + res.status(500).json({ error: "Failed to read session recording" }); + } + }); +} + +/** + * @openapi + * /plugin-api/session-recording/{id}: + * delete: + * summary: Delete session recording + * description: Deletes a session recording and its file. + * tags: + * - Session Recording + * parameters: + * - in: path + * name: id + * required: true + * schema: { type: integer } + * responses: + * 200: + * description: Session recording deleted. + * 404: + * description: Session recording not found. + * 500: + * description: Failed to delete session recording. + */ +function registerDelete( + router: Router, + ctx: PluginContext, + repository: SessionRecordingRepository, + dataDir: () => Promise, +): void { + router.delete("/:id", async (req: Request, res: Response) => { + const userId = actorId(ctx); + if (!userId) return res.status(401).json({ error: "Not authenticated" }); + const rawId = Array.isArray(req.params.id) + ? req.params.id[0] + : req.params.id; + const id = parseInt(rawId, 10); + if (isNaN(id)) return res.status(400).json({ error: "Invalid id" }); + + try { + const row = await repository.findPathByIdForUser(userId, id); + if (!row) return res.status(404).json({ error: "Not found" }); + + const filePath = row.recordingPath; + const deleted = await repository.deleteForUser(userId, id); + if (!deleted) return res.status(404).json({ error: "Not found" }); + + if (filePath) { + const resolvedPath = path.resolve(filePath); + const base = `${path.resolve(await dataDir())}${path.sep}`; + if (resolvedPath.startsWith(base) && fs.existsSync(resolvedPath)) { + await fs.promises.unlink(resolvedPath).catch(() => {}); + } + } + + res.json({ success: true }); + } catch (error) { + ctx.log.error( + "Failed to delete session recording", + error instanceof Error ? error : new Error(String(error)), + ); + res.status(500).json({ error: "Failed to delete session recording" }); + } + }); +} + +export function registerSessionRecordingRoutes( + ctx: PluginContext, + repository: SessionRecordingRepository, + dataDir: () => Promise, +): Router { + const router = express.Router(); + registerList(router, ctx, repository); + registerGet(router, ctx, repository); + registerContent(router, ctx, repository, dataDir); + registerDelete(router, ctx, repository, dataDir); + return router; +} diff --git a/plugins/session-recording/src/backend/service.ts b/plugins/session-recording/src/backend/service.ts new file mode 100644 index 000000000..713c3cc31 --- /dev/null +++ b/plugins/session-recording/src/backend/service.ts @@ -0,0 +1,116 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { SessionRecordingRepository } from "./repository.js"; + +/** + * recordings.writer v1, the service ssh-terminal calls to record sessions. + * + * One call per already-batched chunk (ssh-terminal coalesces on a 300ms + * trailing edge, issue #1049): mkdir and writeFile on the first append, + * appendFile after that, never per chunk. + */ +export interface RecordingSink { + append: (chunk: string) => Promise; + persist: (summary: { + endedAt: number; + durationSeconds: number; + terminatedByOwner: boolean; + terminationReason: string | null; + }) => Promise; + discard: () => void; +} + +export interface RecordingsWriterV1 { + /** Null when recording is off for this user or host. */ + open: (meta: { + sessionId: string; + hostId: number; + userId: string; + protocol: "ssh"; + format: "asciicast"; + startedAt: number; + }) => Promise; + /** + * Inserts a row for a recording a caller already wrote to disk itself + * (remote desktop's guacd recordings), rather than streaming through + * open()/append(). The caller already decided to record; this only writes + * the row. + */ + createFinished: (input: { + hostId: number; + userId: string; + startedAt: string; + endedAt: string; + duration: number | null; + recordingPath: string; + protocol: string; + format: string; + }) => Promise<{ id: number }>; +} + +export function createRecordingsWriter( + ctx: PluginContext, + repository: SessionRecordingRepository, +): RecordingsWriterV1 { + return { + async open(meta) { + const enabled = await ctx.settings.getHost( + meta.hostId, + "enableSessionRecording", + ); + if (enabled === false) return null; + + const dataDir = await ctx.files.dataDir(); + const dir = path.join(dataDir, "session_logs", meta.userId); + const filePath = path.join(dir, `${meta.sessionId}.cast`); + let wroteFirst = false; + let discarded = false; + + const username = await repository.usernameFor(meta.userId); + const row = await repository.create({ + hostId: meta.hostId, + userId: meta.userId, + username, + startedAt: new Date(meta.startedAt).toISOString(), + recordingPath: filePath, + protocol: meta.protocol, + format: meta.format, + }); + + return { + async append(chunk: string) { + if (discarded) return; + if (!wroteFirst) { + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(filePath, chunk); + wroteFirst = true; + } else { + await fs.appendFile(filePath, chunk); + } + }, + + async persist(summary) { + if (discarded) return; + await repository.updateEnded(row.id, { + endedAt: new Date(summary.endedAt).toISOString(), + duration: summary.durationSeconds, + terminatedByOwner: summary.terminatedByOwner, + terminationReason: summary.terminationReason, + }); + }, + + discard() { + discarded = true; + void repository.deleteById(row.id).catch(() => {}); + }, + }; + }, + + async createFinished(input) { + const username = await repository.usernameFor(input.userId); + const row = await repository.create({ ...input, username }); + return { id: row.id }; + }, + }; +} diff --git a/plugins/session-recording/src/backend/tables.ts b/plugins/session-recording/src/backend/tables.ts new file mode 100644 index 000000000..bb32940a6 --- /dev/null +++ b/plugins/session-recording/src/backend/tables.ts @@ -0,0 +1,52 @@ +import { + adoptLegacyTable, + boolean, + defineTable, + id, + integer, + refHost, + text, + timestamp, + varchar, +} from "@termix/plugin-sdk/db"; + +/** + * userId is a plain column, not refUser(): a recording is evidence about the + * host as much as the person, so it outlives the account. The plugin listens + * for user.deleted and clears userId itself instead of cascading. username + * keeps an anonymized row attributable. + */ +export const sessionRecordings = adoptLegacyTable( + "session_recordings", + defineTable( + "session_recordings", + { + id: id(), + hostId: refHost(), + userId: varchar(), + username: text(), + accessId: integer(), + startedAt: timestamp().notNull().defaultNow(), + endedAt: timestamp(), + duration: integer(), + commands: text(), + dangerousActions: text(), + recordingPath: text(), + protocol: text().notNull().default("ssh"), + format: text().notNull().default("text"), + terminatedByOwner: boolean().default(false), + terminationReason: text(), + }, + { + indexes: [ + { + name: "idx_session_recordings_user_started", + columns: ["userId", "startedAt"], + }, + { name: "idx_session_recordings_host", columns: ["hostId"] }, + ], + }, + ), +); + +export const tables = [sessionRecordings]; diff --git a/plugins/session-recording/src/frontend/RecordingStatus.tsx b/plugins/session-recording/src/frontend/RecordingStatus.tsx new file mode 100644 index 000000000..c8ee32dbe --- /dev/null +++ b/plugins/session-recording/src/frontend/RecordingStatus.tsx @@ -0,0 +1,21 @@ +import { Circle } from "lucide-react"; +import { useTranslation } from "@termix/plugin-sdk/frontend"; + +/** What ssh-terminal hands a "terminal.toolbarStatus" component. */ +interface TerminalToolbarStatusProps { + host?: { pluginSettings?: Record> }; + isConnected?: boolean; +} + +export function RecordingStatus(props: Record) { + const { isConnected } = props as TerminalToolbarStatusProps; + const { t } = useTranslation(); + if (!isConnected) return null; + + return ( +
+ + {t("terminalStatus.recording")} +
+ ); +} diff --git a/src/ui/sidebar/SessionLogsPanel.tsx b/plugins/session-recording/src/frontend/SessionLogsPanel.tsx similarity index 86% rename from src/ui/sidebar/SessionLogsPanel.tsx rename to plugins/session-recording/src/frontend/SessionLogsPanel.tsx index bb29290db..05dca69a6 100644 --- a/src/ui/sidebar/SessionLogsPanel.tsx +++ b/plugins/session-recording/src/frontend/SessionLogsPanel.tsx @@ -1,14 +1,18 @@ import { useState, useEffect, useCallback, useRef } from "react"; -import { useTranslation } from "react-i18next"; -import { copyToClipboard } from "@/lib/clipboard"; -import { useAdaptivePolling } from "@/hooks/use-adaptive-polling"; -import { Input } from "@/components/input"; import { + useTranslation, + useToast, + usePluginApi, + usePermission, +} from "@termix/plugin-sdk/frontend"; +import { + Input, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/tooltip"; + copyToClipboard, +} from "@termix/plugin-sdk/ui"; import { ArrowLeft, Check, @@ -17,26 +21,58 @@ import { Eye, FileText, Loader2, - Save, ScrollText, Search, X, } from "lucide-react"; -import { toast } from "sonner"; +import { SessionRecordingPlayer } from "./SessionRecordingPlayer"; +import { asciicastToPlainText, parseAsciicast } from "./asciicast"; import { - getSessionLogs, - getSessionLogContent, - getSessionLogBlob, - getSessionRecordingRetention, - setSessionRecordingRetention, - deleteSessionLog, + createSessionRecordingApi, type SessionLogRecord, -} from "@/api/session-log-api"; -import { SessionRecordingPlayer } from "@/features/session-recording/SessionRecordingPlayer"; -import { - asciicastToPlainText, - parseAsciicast, -} from "@/features/session-recording/asciicast"; +} from "./session-recording-api"; + +function useAdaptivePolling( + fn: () => Promise, + options: { minIntervalMs: number; maxIntervalMs: number }, + enabled: boolean, +) { + const fnRef = useRef(fn); + fnRef.current = fn; + + useEffect(() => { + if (!enabled) return; + let interval = options.minIntervalMs; + let stable = 0; + let cancelled = false; + let timer: ReturnType; + + const tick = async () => { + if (cancelled) return; + try { + const changed = await fnRef.current(); + if (changed) { + interval = options.minIntervalMs; + stable = 0; + } else { + stable++; + if (stable >= 3) { + interval = Math.min(interval * 1.5, options.maxIntervalMs); + } + } + } catch { + // keep polling + } + if (!cancelled) timer = setTimeout(tick, interval); + }; + + timer = setTimeout(tick, interval); + return () => { + cancelled = true; + clearTimeout(timer); + }; + }, [enabled, options.minIntervalMs, options.maxIntervalMs]); +} function formatDuration(seconds: number | null): string { if (seconds == null) return "--"; @@ -206,6 +242,9 @@ function LogRow({ export function SessionLogsPanel() { const { t } = useTranslation(); + const toast = useToast(); + const api = createSessionRecordingApi(usePluginApi()); + const canView = usePermission("view"); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState(""); @@ -219,8 +258,6 @@ export function SessionLogsPanel() { ); const [deleting, setDeleting] = useState(false); const [copied, setCopied] = useState(false); - const [retentionDays, setRetentionDays] = useState(null); - const [savingRetention, setSavingRetention] = useState(false); const logsRef = useRef(logs); logsRef.current = logs; @@ -228,7 +265,7 @@ export function SessionLogsPanel() { async (initial = false) => { if (initial) setLoading(true); try { - const fresh = await getSessionLogs(); + const fresh = await api.list(); const changed = logsRef.current.length !== fresh.length || logsRef.current.some((log, i) => log.id !== fresh[i]?.id); @@ -247,25 +284,21 @@ export function SessionLogsPanel() { if (initial) setLoading(false); } }, - [t], + [api, t, toast], ); useEffect(() => { + if (!canView) return; void load(true); - getSessionRecordingRetention() - .then(setRetentionDays) - .catch(() => setRetentionDays(null)); - }, [load]); + }, [canView, load]); useAdaptivePolling( () => load(false), { minIntervalMs: 5_000, maxIntervalMs: 30_000, - stablePollsPerStep: 3, }, - true, - { runImmediately: false }, + canView, ); const q = filter.trim().toLowerCase(); @@ -285,11 +318,11 @@ export function SessionLogsPanel() { setViewLoading(true); try { if (log.format === "text") { - const text = await getSessionLogContent(log.id); + const text = await api.getContent(log.id); setViewContent(text); setViewText(text); } else { - const blob = await getSessionLogBlob(log.id); + const blob = await api.getBlob(log.id); setViewBlob(blob); setViewText(await extractPlainText(log, blob)); } @@ -311,7 +344,7 @@ export function SessionLogsPanel() { const handleDownload = async (log: SessionLogRecord) => { try { - downloadBlob(await getSessionLogBlob(log.id), buildFilename(log)); + downloadBlob(await api.getBlob(log.id), buildFilename(log)); } catch { toast.error(t("sessionLogs.loadError")); } @@ -322,7 +355,7 @@ export function SessionLogsPanel() { const text = log.id === viewLog?.id && viewText != null ? viewText - : await extractPlainText(log, await getSessionLogBlob(log.id)); + : await extractPlainText(log, await api.getBlob(log.id)); if (text == null) { toast.error(t("sessionLogs.loadError")); return; @@ -340,7 +373,7 @@ export function SessionLogsPanel() { if (!deleteTarget) return; setDeleting(true); try { - await deleteSessionLog(deleteTarget.id); + await api.delete(deleteTarget.id); setLogs((prev) => prev.filter((l) => l.id !== deleteTarget.id)); setDeleteTarget(null); } catch { @@ -473,44 +506,6 @@ export function SessionLogsPanel() { return (
- {retentionDays != null && ( -
- Recording retention - setRetentionDays(Number(event.target.value))} - className="h-7 w-20 text-xs" - aria-label="Recording retention days" - /> - days - -
- )} {logs.length === 0 ? (
diff --git a/src/ui/features/session-recording/SessionRecordingPlayer.tsx b/plugins/session-recording/src/frontend/SessionRecordingPlayer.tsx similarity index 97% rename from src/ui/features/session-recording/SessionRecordingPlayer.tsx rename to plugins/session-recording/src/frontend/SessionRecordingPlayer.tsx index 7561f31a3..6bfe04244 100644 --- a/src/ui/features/session-recording/SessionRecordingPlayer.tsx +++ b/plugins/session-recording/src/frontend/SessionRecordingPlayer.tsx @@ -1,13 +1,12 @@ -import { getErrorMessage } from "../../lib/error-message.js"; import { useCallback, useEffect, useRef, useState } from "react"; import { Pause, Play } from "lucide-react"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import Guacamole from "guacamole-common-js"; -import type { SessionLogRecord } from "@/api/session-log-api"; -import { parseAsciicast, type Asciicast } from "./asciicast"; +import { Select2 } from "@termix/plugin-sdk/ui"; import "@xterm/xterm/css/xterm.css"; -import { Select2 } from "@/components/select2"; +import { parseAsciicast, type Asciicast } from "./asciicast"; +import type { SessionLogRecord } from "./session-recording-api"; const SPEEDS = [0.5, 1, 2, 4]; @@ -16,6 +15,10 @@ function formatPosition(seconds: number) { return `${minutes}:${String(Math.floor(seconds % 60)).padStart(2, "0")}`; } +function getErrorMessage(error: unknown, fallback: string): string { + return error instanceof Error ? error.message : fallback; +} + function PlaybackControls({ playing, position, diff --git a/src/ui/features/session-recording/asciicast.ts b/plugins/session-recording/src/frontend/asciicast.ts similarity index 100% rename from src/ui/features/session-recording/asciicast.ts rename to plugins/session-recording/src/frontend/asciicast.ts diff --git a/plugins/session-recording/src/frontend/index.tsx b/plugins/session-recording/src/frontend/index.tsx new file mode 100644 index 000000000..35b66413d --- /dev/null +++ b/plugins/session-recording/src/frontend/index.tsx @@ -0,0 +1,42 @@ +import { ScrollText } from "lucide-react"; +import type { TermixApp } from "@termix/plugin-sdk/frontend"; +import { SessionLogsPanel } from "./SessionLogsPanel"; +import { RecordingStatus } from "./RecordingStatus"; + +export function activate(app: TermixApp): void { + app.registerRailItem({ + id: "session-logs", + icon: ScrollText, + titleKey: "nav.sessionLogs", + kind: "panel", + promotable: true, + rightDockable: true, + separatorAfter: true, + permission: "view", + }); + + app.registerPanel("session-logs", () => ); + + app.registerTab("session-logs", () => , { + icon: ScrollText, + titleKey: "nav.sessionLogs", + hostless: true, + }); + + app.registerSlotContribution("terminal.toolbarStatus", { + actionId: "session-recording.terminalStatus", + titleKey: "nav.sessionLogs", + kind: "component", + component: RecordingStatus, + when: (context) => { + const host = context.host as + | { pluginSettings?: Record> } + | undefined; + const enabled = + host?.pluginSettings?.["session-recording"]?.enableSessionRecording; + return enabled !== false; + }, + }); +} + +export function deactivate(): void {} diff --git a/plugins/session-recording/src/frontend/session-recording-api.ts b/plugins/session-recording/src/frontend/session-recording-api.ts new file mode 100644 index 000000000..17e6b9c7d --- /dev/null +++ b/plugins/session-recording/src/frontend/session-recording-api.ts @@ -0,0 +1,44 @@ +import type { PluginApiClient } from "@termix/plugin-sdk/frontend"; + +export type SessionLogRecord = { + id: number; + hostId: number; + userId: string | null; + startedAt: string; + endedAt: string | null; + duration: number | null; + recordingPath: string | null; + hostName: string | null; + hostIp: string | null; + sizeBytes: number | null; + protocol: "ssh" | "rdp" | "vnc" | "telnet"; + format: "text" | "asciicast" | "guacamole"; + username: string | null; +}; + +export function createSessionRecordingApi(api: PluginApiClient) { + return { + async list(): Promise { + const response = await api.get<{ logs: SessionLogRecord[] }>("/"); + return response.data.logs; + }, + + async getBlob(id: number): Promise { + const response = await api.get(`/${id}/content`, { + responseType: "blob", + }); + return response.data; + }, + + async getContent(id: number): Promise { + const response = await api.get(`/${id}/content`, { + responseType: "text", + }); + return response.data; + }, + + async delete(id: number): Promise { + await api.delete(`/${id}`); + }, + }; +} diff --git a/plugins/session-recording/tests/backend/helpers.ts b/plugins/session-recording/tests/backend/helpers.ts new file mode 100644 index 000000000..9f5334bf5 --- /dev/null +++ b/plugins/session-recording/tests/backend/helpers.ts @@ -0,0 +1,128 @@ +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { fileURLToPath } from "node:url"; +import express, { type Router } from "express"; +import { + createMockCtx, + createTestDb, + type MockPluginContext, + type TestDb, +} from "@termix/plugin-sdk/testing"; +import type { PluginHostSummary } from "@termix/plugin-sdk/backend"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import manifestJson from "../../manifest.json"; + +export const pluginDir = fileURLToPath(new URL("../..", import.meta.url)); +export const manifest = manifestJson as unknown as PluginManifest; + +export function host( + overrides: Partial = {}, +): PluginHostSummary { + return { + id: 7, + userId: "user-1", + name: "web", + ip: "10.0.0.5", + port: 22, + username: "root", + tags: null, + folder: null, + authType: "password", + ...overrides, + }; +} + +export interface TestServer { + db: TestDb; + mock: MockPluginContext; + request: ( + method: string, + path: string, + options?: { user?: string; body?: unknown }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) => Promise<{ status: number; body: any }>; + requestBinary: ( + method: string, + path: string, + options?: { user?: string }, + ) => Promise<{ status: number; body: ArrayBuffer; contentType: string }>; + close: () => Promise; +} + +export async function startServer( + options: { + permissions?: string[]; + users?: string[]; + hosts?: PluginHostSummary[]; + } = {}, +): Promise { + const db = await createTestDb(pluginDir); + for (const user of options.users ?? ["user-1", "user-2"]) { + db.sqlite + .prepare("INSERT INTO users (id, username) VALUES (?, ?)") + .run(user, user); + } + for (const h of options.hosts ?? [host()]) { + db.sqlite.prepare("INSERT INTO ssh_data (id) VALUES (?)").run(h.id); + } + + let router: Router | null = null; + const mock = createMockCtx({ + pluginId: manifest.id, + manifest, + capabilities: manifest.capabilities, + db: db.database, + router: () => (router = express.Router()), + permissions: options.permissions ?? ["session-recording.view"], + hosts: options.hosts ?? [host()], + }); + + const { activate } = await import("../../src/backend/index.js"); + await activate(mock.ctx); + + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + mock.setActor(req.header("x-test-user") ?? undefined); + next(); + }); + app.use((req, res, next) => router!(req, res, next)); + + const server = http.createServer(app); + await new Promise((resolve) => server.listen(0, resolve)); + const { port } = server.address() as AddressInfo; + + return { + db, + mock, + async request(method, path, { user = "user-1", body: given } = {}) { + const body = method === "GET" ? undefined : given; + const response = await fetch(`http://127.0.0.1:${port}${path}`, { + method, + headers: { + "x-test-user": user, + ...(body !== undefined ? { "content-type": "application/json" } : {}), + }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + const text = await response.text(); + return { status: response.status, body: text ? JSON.parse(text) : null }; + }, + async requestBinary(method, path, { user = "user-1" } = {}) { + const response = await fetch(`http://127.0.0.1:${port}${path}`, { + method, + headers: { "x-test-user": user }, + }); + return { + status: response.status, + body: await response.arrayBuffer(), + contentType: response.headers.get("content-type") ?? "", + }; + }, + async close() { + for (const dispose of [...mock.disposals].reverse()) await dispose(); + await new Promise((resolve) => server.close(() => resolve())); + db.close(); + }, + }; +} diff --git a/plugins/session-recording/tests/backend/migrations.test.ts b/plugins/session-recording/tests/backend/migrations.test.ts new file mode 100644 index 000000000..f817fab03 --- /dev/null +++ b/plugins/session-recording/tests/backend/migrations.test.ts @@ -0,0 +1,141 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing"; +import { pluginDir } from "./helpers"; + +// session_recordings as core's SQLite bootstrap created it before 2.9.0. +const LEGACY_DDL = ` + CREATE TABLE session_recordings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NOT NULL, + user_id TEXT, + username TEXT, + access_id INTEGER, + started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + ended_at TEXT, + duration INTEGER, + commands TEXT, + dangerous_actions TEXT, + recording_path TEXT, + protocol TEXT NOT NULL DEFAULT 'ssh', + format TEXT NOT NULL DEFAULT 'text', + terminated_by_owner INTEGER DEFAULT 0, + termination_reason TEXT, + FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL + ); +`; + +let db: TestDb | null = null; + +afterEach(() => { + db?.close(); + db = null; +}); + +const tableExists = (name: string) => + !!db!.sqlite + .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?") + .get(name); + +describe("adopting session_recordings", () => { + it("keeps every existing row when the legacy table is there", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1')"); + sqlite.exec("INSERT INTO ssh_data (id) VALUES (7)"); + sqlite.exec(LEGACY_DDL); + sqlite + .prepare( + "INSERT INTO session_recordings (host_id, user_id, username, recording_path, protocol, format) VALUES (?, ?, ?, ?, ?, ?)", + ) + .run( + 7, + "user-1", + "user-1", + "/data/session_logs/user-1/a.cast", + "ssh", + "asciicast", + ); + }, + }); + + expect(db.applied).toEqual(["0001_adopt_session_recordings"]); + expect(tableExists("session_recordings")).toBe(false); + expect(tableExists("p_session_recording_session_recordings")).toBe(true); + + expect( + db.sqlite + .prepare( + "SELECT host_id, user_id, recording_path, protocol, format FROM p_session_recording_session_recordings", + ) + .all(), + ).toEqual([ + { + host_id: 7, + user_id: "user-1", + recording_path: "/data/session_logs/user-1/a.cast", + protocol: "ssh", + format: "asciicast", + }, + ]); + }); + + it("creates the table fresh when there is no legacy one", async () => { + db = await createTestDb(pluginDir); + + expect(tableExists("session_recordings")).toBe(false); + expect(tableExists("p_session_recording_session_recordings")).toBe(true); + expect( + db.sqlite + .prepare("SELECT * FROM p_session_recording_session_recordings") + .all(), + ).toEqual([]); + }); + + it("cascades on host delete", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1')"); + sqlite.exec("INSERT INTO ssh_data (id) VALUES (7)"); + }, + }); + db.sqlite + .prepare( + "INSERT INTO p_session_recording_session_recordings (host_id, user_id, protocol, format) VALUES (?, ?, ?, ?)", + ) + .run(7, "user-1", "ssh", "asciicast"); + + db.sqlite.exec("DELETE FROM ssh_data WHERE id = 7"); + + expect( + db.sqlite + .prepare("SELECT * FROM p_session_recording_session_recordings") + .all(), + ).toEqual([]); + }); + + it("keeps a row whose user was deleted, with userId cleared", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1')"); + sqlite.exec("INSERT INTO ssh_data (id) VALUES (7)"); + }, + }); + db.sqlite + .prepare( + "INSERT INTO p_session_recording_session_recordings (host_id, user_id, protocol, format) VALUES (?, ?, ?, ?)", + ) + .run(7, "user-1", "ssh", "asciicast"); + + // userId is a plain column, not refUser(): deleting the user must not + // cascade delete the row (the plugin clears userId itself on + // user.deleted instead). + db.sqlite.exec("DELETE FROM users WHERE id = 'user-1'"); + + expect( + db.sqlite + .prepare("SELECT host_id FROM p_session_recording_session_recordings") + .all(), + ).toEqual([{ host_id: 7 }]); + }); +}); diff --git a/plugins/session-recording/tests/backend/repository.test.ts b/plugins/session-recording/tests/backend/repository.test.ts new file mode 100644 index 000000000..c825cd50e --- /dev/null +++ b/plugins/session-recording/tests/backend/repository.test.ts @@ -0,0 +1,145 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing"; +import { pluginDir } from "./helpers"; +import { createSessionRecordingRepository } from "../../src/backend/repository"; +import { sessionRecordings } from "../../src/backend/tables"; + +let db: TestDb | null = null; + +afterEach(() => { + db?.close(); + db = null; +}); + +const fakeHosts = { + list: async () => [ + { + id: 7, + userId: "user-1", + name: "web", + ip: "10.0.0.5", + port: 22, + username: "root", + tags: null, + folder: null, + authType: "password", + }, + ], + get: async () => null, + checkAccess: async () => ({ + hasAccess: false, + isOwner: false, + isShared: false, + }), + create: async () => { + throw new Error("not used"); + }, + update: async () => null, + listOwned: async () => [], + share: async () => ({ hostId: 0, shared: false }), + listUsers: async () => [], + listRoles: async () => [], + trackSession: () => () => {}, + recordActivity: async () => {}, +}; + +async function setup() { + db = await createTestDb(pluginDir); + db.sqlite + .prepare("INSERT INTO users (id, username) VALUES (?, ?)") + .run("user-1", "alice"); + db.sqlite.prepare("INSERT INTO ssh_data (id) VALUES (?)").run(7); + const table = await db.database.define(sessionRecordings); + return createSessionRecordingRepository(db.database, table, fakeHosts); +} + +describe("session recording repository", () => { + it("creates a row and reads it back by recordingPath", async () => { + const repo = await setup(); + const row = await repo.create({ + hostId: 7, + userId: "user-1", + username: "alice", + startedAt: new Date().toISOString(), + recordingPath: "/data/session_logs/user-1/a.cast", + protocol: "ssh", + format: "asciicast", + }); + + expect(row.id).toBeGreaterThan(0); + expect(row.recordingPath).toBe("/data/session_logs/user-1/a.cast"); + expect(db!.persisted).toBeGreaterThan(0); + }); + + it("lists a user's recordings with host name and ip attached", async () => { + const repo = await setup(); + await repo.create({ + hostId: 7, + userId: "user-1", + startedAt: new Date().toISOString(), + recordingPath: "/data/session_logs/user-1/a.cast", + protocol: "ssh", + format: "asciicast", + }); + + const rows = await repo.listByUserIdWithHost("user-1"); + expect(rows).toHaveLength(1); + expect(rows[0].hostName).toBe("web"); + expect(rows[0].hostIp).toBe("10.0.0.5"); + }); + + it("finds and deletes only the owner's row", async () => { + const repo = await setup(); + const row = await repo.create({ + hostId: 7, + userId: "user-1", + startedAt: new Date().toISOString(), + recordingPath: "/data/session_logs/user-1/a.cast", + protocol: "ssh", + format: "asciicast", + }); + + expect(await repo.findByIdForUser("user-2", row.id)).toBeNull(); + expect(await repo.deleteForUser("user-2", row.id)).toBe(false); + expect(await repo.deleteForUser("user-1", row.id)).toBe(true); + expect(await repo.findByIdForUser("user-1", row.id)).toBeNull(); + }); + + it("anonymizes a user's rows instead of deleting them", async () => { + const repo = await setup(); + const row = await repo.create({ + hostId: 7, + userId: "user-1", + startedAt: new Date().toISOString(), + recordingPath: "/data/session_logs/user-1/a.cast", + protocol: "ssh", + format: "asciicast", + }); + + await repo.anonymizeByUserId("user-1"); + + const rows = await repo.listPathsOlderThan( + new Date(Date.now() + 60_000).toISOString(), + ); + expect(rows.map((r) => r.id)).toContain(row.id); + expect(await repo.findByIdForUser("user-1", row.id)).toBeNull(); + }); + + it("lists recordings older than a cutoff for retention pruning", async () => { + const repo = await setup(); + await repo.create({ + hostId: 7, + userId: "user-1", + startedAt: "2020-01-01T00:00:00.000Z", + recordingPath: "/data/session_logs/user-1/old.cast", + protocol: "ssh", + format: "asciicast", + }); + + const old = await repo.listPathsOlderThan("2021-01-01T00:00:00.000Z"); + expect(old).toHaveLength(1); + + const none = await repo.listPathsOlderThan("2019-01-01T00:00:00.000Z"); + expect(none).toHaveLength(0); + }); +}); diff --git a/plugins/session-recording/tests/backend/routes.test.ts b/plugins/session-recording/tests/backend/routes.test.ts new file mode 100644 index 000000000..e4ee2eccc --- /dev/null +++ b/plugins/session-recording/tests/backend/routes.test.ts @@ -0,0 +1,179 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { startServer, type TestServer } from "./helpers"; + +let server: TestServer | null = null; +let dataDir: string; + +beforeEach(async () => { + dataDir = await fs.mkdtemp( + path.join(os.tmpdir(), "session-recording-routes-"), + ); +}); + +afterEach(async () => { + await server?.close(); + server = null; + await fs.rm(dataDir, { recursive: true, force: true }); +}); + +describe("session recording routes", () => { + it("401s without an authenticated actor", async () => { + server = await startServer(); + const res = await server.request("GET", "/", { user: "" }); + expect(res.status).toBe(401); + }); + + it("lists only the caller's own recordings", async () => { + server = await startServer(); + + const table = await server.mock.ctx.db.client<{ + insert: (t: unknown) => { + values: (v: unknown) => Promise; + }; + }>(); + void table; + + // Seed through the repository the way activate() wired it: easiest is a + // direct HTTP round trip isn't available for writes (only ssh-terminal + // and remote-desktop write through the service), so seed the table + // directly. + server.db.sqlite + .prepare( + `INSERT INTO p_session_recording_session_recordings + (host_id, user_id, recording_path, protocol, format, started_at) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + 7, + "user-1", + "/tmp/a.cast", + "ssh", + "asciicast", + new Date().toISOString(), + ); + server.db.sqlite + .prepare( + `INSERT INTO p_session_recording_session_recordings + (host_id, user_id, recording_path, protocol, format, started_at) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + 7, + "user-2", + "/tmp/b.cast", + "ssh", + "asciicast", + new Date().toISOString(), + ); + + const res = await server.request("GET", "/", { user: "user-1" }); + expect(res.status).toBe(200); + expect(res.body.logs).toHaveLength(1); + expect(res.body.logs[0].userId).toBe("user-1"); + }); + + it("404s getting another user's recording", async () => { + server = await startServer(); + server.db.sqlite + .prepare( + `INSERT INTO p_session_recording_session_recordings + (host_id, user_id, recording_path, protocol, format, started_at) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + 7, + "user-2", + "/tmp/b.cast", + "ssh", + "asciicast", + new Date().toISOString(), + ); + + const res = await server.request("GET", "/1", { user: "user-1" }); + expect(res.status).toBe(404); + }); + + it("serves file content within the plugin's data directory", async () => { + server = await startServer(); + const filePath = path.join(dataDir, "session_logs", "user-1", "a.cast"); + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, '{"version":2}\n[0,"o","hi"]\n'); + + server.db.sqlite + .prepare( + `INSERT INTO p_session_recording_session_recordings + (host_id, user_id, recording_path, protocol, format, started_at) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run(7, "user-1", filePath, "ssh", "asciicast", new Date().toISOString()); + + // Point ctx.files.dataDir() at the temp dir for this request. + ( + server.mock.ctx as unknown as { + files: { dataDir: () => Promise }; + } + ).files = { dataDir: async () => dataDir }; + + const res = await server.requestBinary("GET", "/1/content", { + user: "user-1", + }); + expect(res.status).toBe(200); + expect(res.contentType).toContain("asciicast"); + }); + + it("refuses a path outside the plugin's data directory", async () => { + server = await startServer(); + server.db.sqlite + .prepare( + `INSERT INTO p_session_recording_session_recordings + (host_id, user_id, recording_path, protocol, format, started_at) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + 7, + "user-1", + "/etc/passwd", + "ssh", + "asciicast", + new Date().toISOString(), + ); + + ( + server.mock.ctx as unknown as { + files: { dataDir: () => Promise }; + } + ).files = { dataDir: async () => dataDir }; + + const res = await server.requestBinary("GET", "/1/content", { + user: "user-1", + }); + expect(res.status).toBe(403); + }); + + it("deletes only the caller's own recording", async () => { + server = await startServer(); + server.db.sqlite + .prepare( + `INSERT INTO p_session_recording_session_recordings + (host_id, user_id, recording_path, protocol, format, started_at) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + 7, + "user-1", + "/tmp/a.cast", + "ssh", + "asciicast", + new Date().toISOString(), + ); + + const denied = await server.request("DELETE", "/1", { user: "user-2" }); + expect(denied.status).toBe(404); + + const ok = await server.request("DELETE", "/1", { user: "user-1" }); + expect(ok.status).toBe(200); + }); +}); diff --git a/plugins/session-recording/tests/backend/service.test.ts b/plugins/session-recording/tests/backend/service.test.ts new file mode 100644 index 000000000..311bd2e73 --- /dev/null +++ b/plugins/session-recording/tests/backend/service.test.ts @@ -0,0 +1,174 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + createMockCtx, + createTestDb, + type MockPluginContext, + type TestDb, +} from "@termix/plugin-sdk/testing"; +import manifestJson from "../../manifest.json"; +import { pluginDir } from "./helpers"; +import { createSessionRecordingRepository } from "../../src/backend/repository"; +import { sessionRecordings } from "../../src/backend/tables"; +import { createRecordingsWriter } from "../../src/backend/service"; + +const manifest = + manifestJson as unknown as import("@termix/plugin-sdk/manifest").PluginManifest; + +let db: TestDb | null = null; +let dataDir: string; + +beforeEach(async () => { + dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "session-recording-")); +}); + +afterEach(async () => { + db?.close(); + db = null; + await fs.rm(dataDir, { recursive: true, force: true }); +}); + +async function setup(): Promise<{ + mock: MockPluginContext; + writer: ReturnType; +}> { + db = await createTestDb(pluginDir); + db.sqlite + .prepare("INSERT INTO users (id, username) VALUES (?, ?)") + .run("user-1", "alice"); + db.sqlite.prepare("INSERT INTO ssh_data (id) VALUES (?)").run(7); + const table = await db.database.define(sessionRecordings); + const hosts = { + list: async () => [], + get: async () => null, + checkAccess: async () => ({ + hasAccess: false, + isOwner: false, + isShared: false, + }), + create: async () => { + throw new Error("not used"); + }, + update: async () => null, + listOwned: async () => [], + share: async () => ({ hostId: 0, shared: false }), + listUsers: async () => [], + listRoles: async () => [], + trackSession: () => () => {}, + recordActivity: async () => {}, + }; + const repo = createSessionRecordingRepository(db.database, table, hosts); + + const mock = createMockCtx({ + pluginId: manifest.id, + manifest, + capabilities: manifest.capabilities, + db: db.database, + }); + // Point ctx.files.dataDir() at a real temp dir for this test. + (mock.ctx as unknown as { files: { dataDir: () => Promise } }).files = + { + dataDir: async () => dataDir, + }; + + const writer = createRecordingsWriter(mock.ctx, repo); + return { mock, writer }; +} + +describe("recordings.writer", () => { + it("is null when the host has recording switched off", async () => { + const { mock, writer } = await setup(); + await mock.ctx.settings.setHost(7, "enableSessionRecording", false); + + const sink = await writer.open({ + sessionId: "s1", + hostId: 7, + userId: "user-1", + protocol: "ssh", + format: "asciicast", + startedAt: Date.now(), + }); + + expect(sink).toBeNull(); + }); + + it("writes the first append with writeFile and later ones with appendFile", async () => { + const { writer } = await setup(); + const sink = await writer.open({ + sessionId: "s1", + hostId: 7, + userId: "user-1", + protocol: "ssh", + format: "asciicast", + startedAt: Date.now(), + }); + expect(sink).not.toBeNull(); + + await sink!.append("chunk-one\n"); + await sink!.append("chunk-two\n"); + + const filePath = path.join(dataDir, "session_logs", "user-1", "s1.cast"); + const content = await fs.readFile(filePath, "utf-8"); + expect(content).toBe("chunk-one\nchunk-two\n"); + }); + + it("persist writes the summary onto the row", async () => { + const { writer } = await setup(); + const sink = await writer.open({ + sessionId: "s1", + hostId: 7, + userId: "user-1", + protocol: "ssh", + format: "asciicast", + startedAt: Date.now(), + }); + await sink!.append("x\n"); + + const endedAt = Date.now(); + await sink!.persist({ + endedAt, + durationSeconds: 42, + terminatedByOwner: true, + terminationReason: null, + }); + + // Not thrown means it wrote successfully; deeper row assertions belong + // to repository.test.ts. + }); + + it("discard removes the row and further writes are no-ops", async () => { + const { writer } = await setup(); + const sink = await writer.open({ + sessionId: "s1", + hostId: 7, + userId: "user-1", + protocol: "ssh", + format: "asciicast", + startedAt: Date.now(), + }); + + sink!.discard(); + await sink!.append("should not write"); + + const filePath = path.join(dataDir, "session_logs", "user-1", "s1.cast"); + await expect(fs.access(filePath)).rejects.toThrow(); + }); + + it("createFinished inserts a row for an already-written recording", async () => { + const { writer } = await setup(); + const row = await writer.createFinished({ + hostId: 7, + userId: "user-1", + startedAt: new Date().toISOString(), + endedAt: new Date().toISOString(), + duration: 10, + recordingPath: "/data/session_recordings/guacamole/x.guac", + protocol: "rdp", + format: "guacamole", + }); + + expect(row.id).toBeGreaterThan(0); + }); +}); diff --git a/plugins/session-recording/tests/frontend/activate.test.tsx b/plugins/session-recording/tests/frontend/activate.test.tsx new file mode 100644 index 000000000..4589ad2e3 --- /dev/null +++ b/plugins/session-recording/tests/frontend/activate.test.tsx @@ -0,0 +1,71 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { PluginApiClient } from "@termix/plugin-sdk/frontend"; +import { + renderWithApp, + type RenderedPluginApp, +} from "@termix/plugin-sdk/testing"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import * as plugin from "../../src/frontend/index"; +import manifestJson from "../../manifest.json"; +import locales from "../../locales/en.json"; + +const manifest = manifestJson as unknown as PluginManifest; + +let rendered: RenderedPluginApp | null = null; + +afterEach(async () => { + await rendered?.deactivate(); + rendered = null; +}); + +function stubApi(overrides: Partial = {}): PluginApiClient { + return { + get: vi.fn(async () => ({ data: { logs: [] } })), + post: vi.fn(async () => ({ data: {} })), + put: vi.fn(async () => ({ data: {} })), + patch: vi.fn(async () => ({ data: {} })), + delete: vi.fn(async () => ({ data: {} })), + ...overrides, + } as PluginApiClient; +} + +describe(`${manifest.id} activate`, () => { + it("registers only views its manifest declares", async () => { + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: stubApi(), + }); + + expect(rendered.registered.tabs()).toEqual(["session-logs"]); + expect(rendered.registered.panels()).toEqual(["session-logs"]); + expect(rendered.registered.railItems()).toEqual([ + expect.objectContaining({ id: "session-logs" }), + ]); + expect(rendered.registered.slot("terminal.toolbarStatus")).toEqual([ + "session-recording.terminalStatus", + ]); + }); + + it("removes everything it registered on deactivate", async () => { + const app = await renderWithApp(plugin, { + manifest, + locales, + api: stubApi(), + }); + await app.deactivate(); + expect(app.registered.tabs()).toEqual([]); + expect(app.registered.panels()).toEqual([]); + expect(app.registered.railItems()).toEqual([]); + }); + + it("renders the panel without crashing", async () => { + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: stubApi(), + }); + const element = rendered.renderPanel("session-logs"); + expect(element).toBeTruthy(); + }); +}); diff --git a/src/ui/tests/features/session-recording/asciicast.test.ts b/plugins/session-recording/tests/frontend/asciicast.test.ts similarity index 88% rename from src/ui/tests/features/session-recording/asciicast.test.ts rename to plugins/session-recording/tests/frontend/asciicast.test.ts index 6ebedaaf5..1f7656837 100644 --- a/src/ui/tests/features/session-recording/asciicast.test.ts +++ b/plugins/session-recording/tests/frontend/asciicast.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { parseAsciicast } from "../../../features/session-recording/asciicast"; +import { parseAsciicast } from "../../src/frontend/asciicast"; describe("parseAsciicast", () => { it("parses terminal dimensions and timed input/output", () => { diff --git a/plugins/session-recording/tsconfig.json b/plugins/session-recording/tsconfig.json new file mode 100644 index 000000000..7b35314c3 --- /dev/null +++ b/plugins/session-recording/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@termix/plugin-sdk/tsconfig.plugin.json", + "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"] +} diff --git a/plugins/session-recording/vitest.config.ts b/plugins/session-recording/vitest.config.ts new file mode 100644 index 000000000..31ba004fc --- /dev/null +++ b/plugins/session-recording/vitest.config.ts @@ -0,0 +1,3 @@ +import { pluginVitestConfig } from "@termix/plugin-sdk/vitest-preset"; + +export default pluginVitestConfig(import.meta.url); diff --git a/scripts/shell-plugin-id-allowlist.json b/scripts/shell-plugin-id-allowlist.json index f5674441c..162ff2d02 100644 --- a/scripts/shell-plugin-id-allowlist.json +++ b/scripts/shell-plugin-id-allowlist.json @@ -1,10 +1,6 @@ { "$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/api/session-log-api.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/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." diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index bc51f56ea..1dfeb9fce 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -10,7 +10,6 @@ import hostRoutes from "./routes/host.js"; import alertRoutes from "./routes/alerts.js"; import credentialsRoutes from "./routes/credentials.js"; import sshAuthRoutes from "./routes/ssh-auth-routes.js"; -import sessionLogRoutes from "./routes/session-log-routes.js"; import rbacRoutes from "./routes/rbac.js"; import openTabsRoutes from "./routes/open-tabs.js"; import userPreferencesRoutes from "./routes/user-preferences.js"; @@ -1805,7 +1804,6 @@ app.use("/host", hostRoutes); app.use("/alerts", alertRoutes); app.use("/credentials", credentialsRoutes); app.use("/ssh-auth", sshAuthRoutes); -app.use("/session_logs", sessionLogRoutes); app.use("/rbac", rbacRoutes); app.use("/open-tabs", openTabsRoutes); app.use("/user-preferences", userPreferencesRoutes); diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index 402edde02..f3d9b520c 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -479,27 +479,6 @@ async function initializeCompleteDatabase(): Promise { FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL ); - CREATE TABLE IF NOT EXISTS session_recordings ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - host_id INTEGER NOT NULL, - user_id TEXT, - username TEXT, - access_id INTEGER, - started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - ended_at TEXT, - duration INTEGER, - commands TEXT, - dangerous_actions TEXT, - recording_path TEXT, - protocol TEXT NOT NULL DEFAULT 'ssh', - format TEXT NOT NULL DEFAULT 'text', - terminated_by_owner INTEGER DEFAULT 0, - termination_reason TEXT, - FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL, - FOREIGN KEY (access_id) REFERENCES host_access (id) ON DELETE SET NULL - ); - CREATE TABLE IF NOT EXISTS secret_sources ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, @@ -937,17 +916,6 @@ const relaxPluginGrantGrantedBy = () => { }; const migrateSchema = () => { - addColumnIfNotExists( - "session_recordings", - "protocol", - "TEXT NOT NULL DEFAULT 'ssh'", - ); - addColumnIfNotExists( - "session_recordings", - "format", - "TEXT NOT NULL DEFAULT 'text'", - ); - addColumnIfNotExists("user_preferences", "theme", "TEXT"); addColumnIfNotExists("user_preferences", "font_size", "TEXT"); addColumnIfNotExists("user_preferences", "accent_color", "TEXT"); diff --git a/src/backend/database/db/performance-indexes.ts b/src/backend/database/db/performance-indexes.ts index 2f6f7d502..ebe637ad7 100644 --- a/src/backend/database/db/performance-indexes.ts +++ b/src/backend/database/db/performance-indexes.ts @@ -124,17 +124,6 @@ export const PERFORMANCE_INDEXES: PerformanceIndex[] = [ columns: "user_id", }, - // Recordings and live session sharing. - { - name: "idx_session_recordings_user_started", - table: "session_recordings", - columns: "user_id, started_at", - }, - { - name: "idx_session_recordings_host", - table: "session_recordings", - columns: "host_id", - }, // Automations. { diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index 996a57c1e..511c23b79 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -719,48 +719,6 @@ export const auditLogs = mysqlTable( ], ); -export const sessionRecordings = mysqlTable( - "session_recordings", - { - id: int("id").autoincrement().primaryKey(), - - hostId: int("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - // Nullable on purpose: a recording is evidence about the host as much as the - // person, so it outlives the account. username keeps it attributable. - userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "set null" }), - username: text("username"), - accessId: int("access_id").references(() => hostAccess.id, { - onDelete: "set null", - }), - - startedAt: varchar("started_at", { length: 255 }) - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - endedAt: text("ended_at"), - duration: int("duration"), - - commands: text("commands"), - dangerousActions: text("dangerous_actions"), - - recordingPath: text("recording_path"), - protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"), - format: text("format").notNull().default("text"), - - terminatedByOwner: boolean("terminated_by_owner").default(false), - terminationReason: text("termination_reason"), - }, - // Listed newest-first per user, and audited per host. - (table) => [ - index("idx_session_recordings_user_started").on( - table.userId, - table.startedAt, - ), - index("idx_session_recordings_host").on(table.hostId), - ], -); - export const opksshTokens = mysqlTable( "opkssh_tokens", { diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index 8bd1497d4..839ffa38f 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -720,48 +720,6 @@ export const auditLogs = pgTable( ], ); -export const sessionRecordings = pgTable( - "session_recordings", - { - id: serial("id").primaryKey(), - - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - // Nullable on purpose: a recording is evidence about the host as much as the - // person, so it outlives the account. username keeps it attributable. - userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "set null" }), - username: text("username"), - accessId: integer("access_id").references(() => hostAccess.id, { - onDelete: "set null", - }), - - startedAt: varchar("started_at", { length: 255 }) - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - endedAt: text("ended_at"), - duration: integer("duration"), - - commands: text("commands"), - dangerousActions: text("dangerous_actions"), - - recordingPath: text("recording_path"), - protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"), - format: text("format").notNull().default("text"), - - terminatedByOwner: boolean("terminated_by_owner").default(false), - terminationReason: text("termination_reason"), - }, - // Listed newest-first per user, and audited per host. - (table) => [ - index("idx_session_recordings_user_started").on( - table.userId, - table.startedAt, - ), - index("idx_session_recordings_host").on(table.hostId), - ], -); - export const opksshTokens = pgTable( "opkssh_tokens", { diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index 435070c42..75bf28aab 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -712,50 +712,6 @@ export const auditLogs = sqliteTable( ], ); -export const sessionRecordings = sqliteTable( - "session_recordings", - { - id: integer("id").primaryKey({ autoIncrement: true }), - - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - // Nullable on purpose: a recording is evidence about the host as much as the - // person, so it outlives the account. username keeps it attributable. - userId: text("user_id").references(() => users.id, { onDelete: "set null" }), - username: text("username"), - accessId: integer("access_id").references(() => hostAccess.id, { - onDelete: "set null", - }), - - startedAt: text("started_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - endedAt: text("ended_at"), - duration: integer("duration"), - - commands: text("commands"), - dangerousActions: text("dangerous_actions"), - - recordingPath: text("recording_path"), - protocol: text("protocol").notNull().default("ssh"), - format: text("format").notNull().default("text"), - - terminatedByOwner: integer("terminated_by_owner", { - mode: "boolean", - }).default(false), - terminationReason: text("termination_reason"), - }, - // Listed newest-first per user, and audited per host. - (table) => [ - index("idx_session_recordings_user_started").on( - table.userId, - table.startedAt, - ), - index("idx_session_recordings_host").on(table.hostId), - ], -); - export const opksshTokens = sqliteTable( "opkssh_tokens", { diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts index 4b0e669b0..20b9f485e 100644 --- a/src/backend/database/repositories/factory.ts +++ b/src/backend/database/repositories/factory.ts @@ -37,7 +37,6 @@ import { RbacAccessRepository } from "./rbac-access-repository.js"; import { RbacPermissionRepository } from "./rbac-permission-repository.js"; import { RecentActivityRepository } from "./recent-activity-repository.js"; import { RoleRepository } from "./role-repository.js"; -import { SessionRecordingRepository } from "./session-recording-repository.js"; import { SessionRepository } from "./session-repository.js"; import { SecretSourceRepository } from "./secret-source-repository.js"; import { CredentialAccessRepository } from "./credential-access-repository.js"; @@ -400,13 +399,6 @@ export function createCurrentRoleRepository(): RoleRepository { ); } -export function createCurrentSessionRecordingRepository(): SessionRecordingRepository { - return new SessionRecordingRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("session_recording_repository_write"), - ); -} - export function createCurrentSessionRepository(): SessionRepository { return new SessionRepository( createCurrentRepositoryContext(), diff --git a/src/backend/database/repositories/session-recording-repository.ts b/src/backend/database/repositories/session-recording-repository.ts deleted file mode 100644 index 7961e473e..000000000 --- a/src/backend/database/repositories/session-recording-repository.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { and, desc, eq, inArray, lt } from "drizzle-orm"; -import { hosts, sessionRecordings } from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { rowsAffected } from "./mutation-result.js"; -import { insertReturning } from "./returning.js"; - -export type SessionRecordingRecord = typeof sessionRecordings.$inferSelect; - -export interface SessionRecordingCreateInput { - hostId: number; - userId: string; - startedAt: string; - endedAt?: string | null; - duration?: number | null; - commands?: string | null; - dangerousActions?: string | null; - recordingPath?: string | null; - protocol?: string | null; - format?: string | null; - terminatedByOwner?: boolean | null; - terminationReason?: string | null; -} - -export interface SessionRecordingListRecord { - id: number; - hostId: number; - userId: string; - startedAt: string; - endedAt: string | null; - duration: number | null; - recordingPath: string | null; - protocol: string; - format: string | null; - hostName: string | null; - hostIp: string | null; -} - -export interface SessionRecordingPathRecord { - id: number; - recordingPath: string | null; -} - -export class SessionRecordingRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async create( - input: SessionRecordingCreateInput, - ): Promise { - const [created] = await insertReturning( - this.context, - sessionRecordings, - input, - ); - - await this.afterWrite(); - return created; - } - - async updateEnded( - id: number, - input: { - endedAt: string; - duration: number | null; - terminatedByOwner?: boolean; - terminationReason?: string; - }, - ): Promise { - await this.context.drizzle - .update(sessionRecordings) - .set(input) - .where(eq(sessionRecordings.id, id)); - await this.afterWrite(); - } - - async findById(id: number): Promise { - const rows = await this.context.drizzle - .select() - .from(sessionRecordings) - .where(eq(sessionRecordings.id, id)) - .limit(1); - return rows[0] ?? null; - } - - async findPathById(id: number): Promise< - | (SessionRecordingPathRecord & { - userId: string; - format?: string | null; - }) - | null - > { - const rows = await this.context.drizzle - .select({ - id: sessionRecordings.id, - recordingPath: sessionRecordings.recordingPath, - userId: sessionRecordings.userId, - format: sessionRecordings.format, - }) - .from(sessionRecordings) - .where(eq(sessionRecordings.id, id)) - .limit(1); - return rows[0] ?? null; - } - - async listByUserIdWithHost( - userId: string, - ): Promise { - return this.context.drizzle - .select({ - id: sessionRecordings.id, - hostId: sessionRecordings.hostId, - userId: sessionRecordings.userId, - startedAt: sessionRecordings.startedAt, - endedAt: sessionRecordings.endedAt, - duration: sessionRecordings.duration, - recordingPath: sessionRecordings.recordingPath, - protocol: sessionRecordings.protocol, - format: sessionRecordings.format, - hostName: hosts.name, - hostIp: hosts.ip, - }) - .from(sessionRecordings) - .leftJoin(hosts, eq(sessionRecordings.hostId, hosts.id)) - .where(eq(sessionRecordings.userId, userId)) - .orderBy(desc(sessionRecordings.startedAt)); - } - - async findByIdForUser( - userId: string, - id: number, - ): Promise { - const rows = await this.context.drizzle - .select() - .from(sessionRecordings) - .where( - and(eq(sessionRecordings.id, id), eq(sessionRecordings.userId, userId)), - ) - .limit(1); - - return rows[0] ?? null; - } - - async findPathByIdForUser( - userId: string, - id: number, - ): Promise { - const rows = await this.context.drizzle - .select({ - id: sessionRecordings.id, - recordingPath: sessionRecordings.recordingPath, - }) - .from(sessionRecordings) - .where( - and(eq(sessionRecordings.id, id), eq(sessionRecordings.userId, userId)), - ) - .limit(1); - - return rows[0] ?? null; - } - - async listPathsOlderThan( - cutoff: string, - ): Promise { - return this.context.drizzle - .select({ - id: sessionRecordings.id, - recordingPath: sessionRecordings.recordingPath, - }) - .from(sessionRecordings) - .where(lt(sessionRecordings.startedAt, cutoff)); - } - - async deleteById(id: number): Promise { - const result = await this.context.drizzle - .delete(sessionRecordings) - .where(eq(sessionRecordings.id, id)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result) > 0; - } - - async deleteForUser(userId: string, id: number): Promise { - const result = await this.context.drizzle - .delete(sessionRecordings) - .where( - and(eq(sessionRecordings.id, id), eq(sessionRecordings.userId, userId)), - ); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result) > 0; - } - - /** - * Detaches recordings from a user being deleted instead of removing them. - * A recording is evidence about the host as much as about the person, and the - * file stays on disk regardless — deleting only the row would orphan it. - */ - async anonymizeByUserId(userId: string): Promise { - const result = await this.context.drizzle - .update(sessionRecordings) - .set({ userId: null }) - .where(eq(sessionRecordings.userId, userId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async deleteByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(sessionRecordings) - .where(eq(sessionRecordings.userId, userId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async deleteByHostId(hostId: number): Promise { - const result = await this.context.drizzle - .delete(sessionRecordings) - .where(eq(sessionRecordings.hostId, hostId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async deleteByHostIds(hostIds: number[]): Promise { - if (hostIds.length === 0) { - return 0; - } - - const result = await this.context.drizzle - .delete(sessionRecordings) - .where(inArray(sessionRecordings.hostId, hostIds)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - private async afterWrite(): Promise { - await this.onWrite?.(); - } -} diff --git a/src/backend/database/routes/delete-user-data.ts b/src/backend/database/routes/delete-user-data.ts index d335b2bb5..8abef13ed 100644 --- a/src/backend/database/routes/delete-user-data.ts +++ b/src/backend/database/routes/delete-user-data.ts @@ -22,7 +22,6 @@ import { createCurrentRbacAccessRepository, createCurrentRoleRepository, createCurrentSessionRepository, - createCurrentSessionRecordingRepository, createCurrentSettingsRepository, createCurrentSharedHostSecretsRepository, createCurrentSshCredentialUsageRepository, @@ -61,9 +60,11 @@ export async function deleteUserAndRelatedData( userId, ); - // Retained rather than deleted: these outlive the account by design. - // See anonymizeByUserId on each repository. - await createCurrentSessionRecordingRepository().anonymizeByUserId(userId); + // session_recordings is retained rather than deleted, by design: it + // outlives the account. The session-recording plugin listens for + // user.deleted and anonymizes its own rows instead of cascading. + const { pluginEvents, TOPICS } = await import("../../plugins/events.js"); + pluginEvents.emit(TOPICS.userDeleted, { userId }); await createCurrentRbacAccessRepository().deleteHostAccessForUserReferences( userId, diff --git a/src/backend/database/routes/host-folder-routes.ts b/src/backend/database/routes/host-folder-routes.ts index 486e3cfef..0c5a1e39f 100644 --- a/src/backend/database/routes/host-folder-routes.ts +++ b/src/backend/database/routes/host-folder-routes.ts @@ -9,7 +9,6 @@ import { createCurrentRecentActivityRepository, createCurrentRbacAccessRepository, createCurrentSshCredentialUsageRepository, - createCurrentSessionRecordingRepository, createCurrentSyncTombstoneRepository, } from "../repositories/factory.js"; import { isNonEmptyString } from "./host-normalizers.js"; @@ -397,9 +396,10 @@ export function registerHostFolderRoutes( const hostIds = hostsToDelete.map((host) => host.id); if (hostIds.length > 0) { - // file manager recent/pinned/shortcuts, transfer_recent and command - // history cascade on the host's refHost() foreign key, as the - // file-manager and ssh-terminal plugins' adopted tables. + // file manager recent/pinned/shortcuts, transfer_recent, command + // history and session recordings cascade on the host's refHost() + // foreign key, as the file-manager, ssh-terminal and + // session-recording plugins' adopted tables. await createCurrentSshCredentialUsageRepository().deleteByHostIds( hostIds, @@ -412,10 +412,6 @@ export function registerHostFolderRoutes( await createCurrentRbacAccessRepository().deleteHostAccessForHosts( hostIds, ); - - await createCurrentSessionRecordingRepository().deleteByHostIds( - hostIds, - ); } const { hostSyncIds, folderSyncIds } = diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index 26a77feec..917f30431 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -20,7 +20,6 @@ import { createCurrentOpksshTokenRepository, createCurrentRecentActivityRepository, createCurrentSshCredentialUsageRepository, - createCurrentSessionRecordingRepository, createCurrentRbacAccessRepository, createCurrentRoleRepository, createCurrentHostResolutionRepository, @@ -2524,9 +2523,10 @@ router.delete( const numericHostId = Number(hostId); - // file manager recent/pinned/shortcuts, transfer_recent and command - // history cascade on the host's refHost() foreign key, as the - // file-manager and ssh-terminal plugins' adopted tables. + // file manager recent/pinned/shortcuts, transfer_recent, command + // history and session recordings cascade on the host's refHost() + // foreign key, as the file-manager, ssh-terminal and + // session-recording plugins' adopted tables. await createCurrentSshCredentialUsageRepository().deleteByHostId( numericHostId, @@ -2540,10 +2540,6 @@ router.delete( numericHostId, ); - await createCurrentSessionRecordingRepository().deleteByHostId( - numericHostId, - ); - await createCurrentHostRepository().deleteForUser(userId, numericHostId); if (hostToDelete.syncId) { await createCurrentSyncTombstoneRepository().record( diff --git a/src/backend/database/routes/session-log-routes.ts b/src/backend/database/routes/session-log-routes.ts deleted file mode 100644 index 1a03fcc93..000000000 --- a/src/backend/database/routes/session-log-routes.ts +++ /dev/null @@ -1,362 +0,0 @@ -import type { AuthenticatedRequest } from "../../../types/index.js"; -import express, { type Request, type Response } from "express"; -import fs from "fs"; -import path from "path"; -import { apiLogger } from "../../utils/logger.js"; -import { AuthManager } from "../../utils/auth-manager.js"; -import { PermissionManager } from "../../utils/permission-manager.js"; -import { - createCurrentSessionRecordingRepository, - createCurrentSettingsRepository, - getCurrentSettingValue, -} from "../repositories/factory.js"; - -const router = express.Router(); - -const DATA_DIR = process.env.DATA_DIR ?? "./db/data"; - -const permissionManager = PermissionManager.getInstance(); - -function isAllowedRecordingPath(filePath: string): boolean { - const resolved = path.resolve(filePath); - return ["session_logs", "session_recordings"].some((directory) => { - const base = `${path.resolve(DATA_DIR, directory)}${path.sep}`; - return resolved.startsWith(base); - }); -} - -function getRetentionDays(): number { - const envDays = parseInt( - process.env.SESSION_RECORDING_RETENTION_DAYS || "", - 10, - ); - try { - const configured = parseInt( - getCurrentSettingValue("session_recording_retention_days") || "", - 10, - ); - if (configured >= 1 && configured <= 3650) return configured; - } catch { - // use environment/default below - } - return envDays >= 1 && envDays <= 3650 ? envDays : 30; -} - -async function canAccessRecording( - userId: string, - ownerId: string, -): Promise { - return userId === ownerId || permissionManager.isAdmin(userId); -} - -async function pruneOldLogs(): Promise { - try { - const cutoff = new Date( - Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000, - ).toISOString(); - - const sessionRecordingRepository = - createCurrentSessionRecordingRepository(); - const old = await sessionRecordingRepository.listPathsOlderThan(cutoff); - - for (const row of old) { - if (row.recordingPath) { - const resolved = path.resolve(row.recordingPath); - if (isAllowedRecordingPath(resolved) && fs.existsSync(resolved)) { - await fs.promises.unlink(resolved).catch(() => {}); - } - } - await sessionRecordingRepository.deleteById(row.id); - } - - if (old.length > 0) { - apiLogger.info(`Pruned ${old.length} old session log(s)`, { - operation: "session_log_prune", - count: old.length, - }); - } - } catch (err) { - apiLogger.warn("Failed to prune old session logs", { - operation: "session_log_prune_error", - error: err instanceof Error ? err.message : String(err), - }); - } -} - -// Run prune once at startup, then every 24 hours -pruneOldLogs(); -setInterval(pruneOldLogs, 24 * 60 * 60 * 1000).unref(); - -const authManager = AuthManager.getInstance(); -const authenticateJWT = authManager.createAuthMiddleware(); - -/** - * @openapi - * /session_logs: - * get: - * summary: List session logs - * description: Returns all terminal session recordings for the authenticated user. - * tags: - * - Session Logs - * responses: - * 200: - * description: List of session recordings. - * 500: - * description: Failed to fetch session logs. - */ -router.get("/", authenticateJWT, async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - try { - const rows = - await createCurrentSessionRecordingRepository().listByUserIdWithHost( - userId, - ); - - const records = rows.map((row) => { - let sizeBytes: number | null = null; - if (row.recordingPath) { - try { - sizeBytes = fs.statSync(row.recordingPath).size; - } catch { - // file may have been removed - } - } - return { ...row, sizeBytes }; - }); - - res.json({ logs: records }); - } catch (error) { - apiLogger.error("Failed to fetch session logs", error, { - operation: "session_logs_list", - userId, - }); - res.status(500).json({ error: "Failed to fetch session logs" }); - } -}); - -router.get( - "/retention", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - if (!(await permissionManager.isAdmin(userId))) { - return res.status(403).json({ error: "Admin access required" }); - } - res.json({ retentionDays: getRetentionDays() }); - }, -); - -router.put( - "/retention", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - if (!(await permissionManager.isAdmin(userId))) { - return res.status(403).json({ error: "Admin access required" }); - } - const retentionDays = Number(req.body?.retentionDays); - if ( - !Number.isInteger(retentionDays) || - retentionDays < 1 || - retentionDays > 3650 - ) { - return res - .status(400) - .json({ error: "Retention must be between 1 and 3650 days" }); - } - await createCurrentSettingsRepository().upsert( - "session_recording_retention_days", - String(retentionDays), - ); - void pruneOldLogs(); - res.json({ retentionDays }); - }, -); - -/** - * @openapi - * /session_logs/{id}: - * get: - * summary: Get session log metadata - * description: Returns metadata for a single session recording. - * tags: - * - Session Logs - * parameters: - * - in: path - * name: id - * required: true - * schema: { type: integer } - * responses: - * 200: - * description: Session recording metadata. - * 403: - * description: Not authorized. - * 404: - * description: Session log not found. - * 500: - * description: Failed to fetch session log. - */ -router.get("/:id", authenticateJWT, async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const rawId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id; - const id = parseInt(rawId, 10); - if (isNaN(id)) return res.status(400).json({ error: "Invalid id" }); - - try { - const row = await createCurrentSessionRecordingRepository().findByIdForUser( - userId, - id, - ); - - if (!row) return res.status(404).json({ error: "Not found" }); - res.json({ log: row }); - } catch (error) { - apiLogger.error("Failed to fetch session log", error, { - operation: "session_log_get", - userId, - id, - }); - res.status(500).json({ error: "Failed to fetch session log" }); - } -}); - -/** - * @openapi - * /session_logs/{id}/content: - * get: - * summary: Get session log content - * description: Returns the raw text content of a session log file. - * tags: - * - Session Logs - * parameters: - * - in: path - * name: id - * required: true - * schema: { type: integer } - * responses: - * 200: - * description: Raw log text. - * 403: - * description: Not authorized. - * 404: - * description: Session log or file not found. - * 500: - * description: Failed to read session log. - */ -router.get( - "/:id/content", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const rawId = Array.isArray(req.params.id) - ? req.params.id[0] - : req.params.id; - const id = parseInt(rawId, 10); - if (isNaN(id)) return res.status(400).json({ error: "Invalid id" }); - - try { - const row = - await createCurrentSessionRecordingRepository().findPathByIdForUser( - userId, - id, - ); - - if (!row) return res.status(404).json({ error: "Not found" }); - - const filePath = row.recordingPath; - if (!filePath) - return res.status(404).json({ error: "No recording file" }); - - const resolvedPath = path.resolve(filePath); - if (!isAllowedRecordingPath(resolvedPath)) { - return res.status(403).json({ error: "Forbidden" }); - } - - if (!fs.existsSync(resolvedPath)) { - return res.status(404).json({ error: "File not found" }); - } - - const content = await fs.promises.readFile(resolvedPath); - const format = - (row as { format?: string | null }).format ?? - (row.recordingPath?.endsWith(".cast") ? "asciicast" : "text"); - const contentType = - format === "guacamole" - ? "application/vnd.apache.guacamole.recording" - : format === "asciicast" - ? "application/x-asciicast" - : "text/plain"; - res.type(contentType).send(content); - } catch (error) { - apiLogger.error("Failed to read session log content", error, { - operation: "session_log_content", - userId, - id, - }); - res.status(500).json({ error: "Failed to read session log" }); - } - }, -); - -/** - * @openapi - * /session_logs/{id}: - * delete: - * summary: Delete session log - * description: Deletes a session recording and its log file. - * tags: - * - Session Logs - * parameters: - * - in: path - * name: id - * required: true - * schema: { type: integer } - * responses: - * 200: - * description: Session log deleted. - * 403: - * description: Not authorized. - * 404: - * description: Session log not found. - * 500: - * description: Failed to delete session log. - */ -router.delete("/:id", authenticateJWT, async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const rawId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id; - const id = parseInt(rawId, 10); - if (isNaN(id)) return res.status(400).json({ error: "Invalid id" }); - - try { - const sessionRecordingRepository = - createCurrentSessionRecordingRepository(); - const row = await sessionRecordingRepository.findPathByIdForUser( - userId, - id, - ); - - if (!row) return res.status(404).json({ error: "Not found" }); - - const filePath = row.recordingPath; - - await sessionRecordingRepository.deleteForUser(userId, id); - - if (filePath) { - const resolvedPath = path.resolve(filePath); - if (isAllowedRecordingPath(resolvedPath) && fs.existsSync(resolvedPath)) { - await fs.promises.unlink(resolvedPath).catch(() => {}); - } - } - - res.json({ success: true }); - } catch (error) { - apiLogger.error("Failed to delete session log", error, { - operation: "session_log_delete", - userId, - id, - }); - res.status(500).json({ error: "Failed to delete session log" }); - } -}); - -export default router; diff --git a/src/backend/plugins/ctx.ts b/src/backend/plugins/ctx.ts index 5568a867f..7b05e5f98 100644 --- a/src/backend/plugins/ctx.ts +++ b/src/backend/plugins/ctx.ts @@ -250,6 +250,20 @@ export function createPluginContext( { action: "kv_list" }, ); + const filesDataDir = guarded( + manifest, + "files:own", + async () => { + const fs = await import("node:fs/promises"); + const path = await import("node:path"); + const base = process.env.DATA_DIR ?? "./db/data"; + const dir = path.join(base, "plugins", pluginId); + await fs.mkdir(dir, { recursive: true }); + return dir; + }, + { action: "files_data_dir" }, + ); + const dbDefine = guarded( manifest, "db:own", @@ -422,6 +436,10 @@ export function createPluginContext( list: () => kvList(), }, + files: { + dataDir: () => filesDataDir(), + }, + registry: { provide: (key, value) => { registry.provide(key, value); diff --git a/src/backend/plugins/events.ts b/src/backend/plugins/events.ts index c2239284d..4e98cd4ae 100644 --- a/src/backend/plugins/events.ts +++ b/src/backend/plugins/events.ts @@ -47,6 +47,13 @@ export const TOPICS = { * the row is not gone, only unrecoverable, and should go with it. */ userDataWiped: "user.data_wiped", + /** + * A user account was deleted. For a plugin whose rows should outlive the + * account (evidence, audit trail) rather than cascade with a refUser() + * column: it keeps a plain userId column and anonymizes its own rows here, + * the way core's own session recordings did before this event existed. + */ + userDeleted: "user.deleted", } as const; export type EventListener = (payload: unknown) => void; diff --git a/src/backend/starter.ts b/src/backend/starter.ts index 1782af8bb..a1e8784a7 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -373,6 +373,10 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { const { runSessionSharingSettingsMigration } = await import("./utils/crypto-migration/session-sharing-settings-migration.js"); await runSessionSharingSettingsMigration(); + + const { runSessionRecordingSettingsMigration } = + await import("./utils/crypto-migration/session-recording-settings-migration.js"); + await runSessionRecordingSettingsMigration(); } catch (error) { systemLogger.warn("Plugin runtime failed to initialize", { operation: "plugin_init", diff --git a/src/backend/tests/database/db/bootstrap-covers-all-tables.test.ts b/src/backend/tests/database/db/bootstrap-covers-all-tables.test.ts index 9760bc842..b591da098 100644 --- a/src/backend/tests/database/db/bootstrap-covers-all-tables.test.ts +++ b/src/backend/tests/database/db/bootstrap-covers-all-tables.test.ts @@ -7,10 +7,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; /** * `migrateSchema()` carried a `SELECT id FROM LIMIT 1` probe for a * number of tables that the primary bootstrap already creates. The probe never - * threw, so the `CREATE TABLE IF NOT EXISTS` in its catch never ran — and two + * threw, so the `CREATE TABLE IF NOT EXISTS` in its catch never ran — and one * of those unreachable copies had drifted away from the real definition - * (`sessions` had lost `ON DELETE CASCADE`; `session_recordings` still had the - * pre-#1128 `user_id NOT NULL` with `ON DELETE CASCADE` and no `username`). + * (`sessions` had lost `ON DELETE CASCADE`). * * They are gone now. What has to stay true is that the bootstrap alone * produces every one of those tables, from an empty database and from a @@ -27,7 +26,6 @@ describe("bootstrap creates the tables the removed probes covered", () => { "roles", "user_roles", "audit_logs", - "session_recordings", "api_keys", ]; @@ -99,16 +97,6 @@ describe("bootstrap creates the tables the removed probes covered", () => { await db.initializeDatabase(); const sqlite = db.getSqlite(); - // session_recordings: nullable user_id with the attribution kept, per - // "audit trails survive the account" — not the NOT NULL + CASCADE the - // dead copy still carried. - const columns = sqlite - .prepare("PRAGMA table_info(session_recordings)") - .all() as Array<{ name: string; notnull: number }>; - const userId = columns.find((c) => c.name === "user_id"); - expect(userId?.notnull).toBe(0); - expect(columns.some((c) => c.name === "username")).toBe(true); - // sessions: the dead copy had dropped ON DELETE CASCADE. const sql = sqlite .prepare( diff --git a/src/backend/tests/database/repositories/session-recording-repository.test.ts b/src/backend/tests/database/repositories/session-recording-repository.test.ts deleted file mode 100644 index 9da43e445..000000000 --- a/src/backend/tests/database/repositories/session-recording-repository.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { TestSqliteDatabase } from "./test-support.js"; -import { SessionRecordingRepository } from "../../../database/repositories/session-recording-repository.js"; - -describe("SessionRecordingRepository", () => { - let adapter: TestSqliteDatabase | null = null; - - afterEach(async () => { - if (adapter) { - await adapter.close(); - adapter = null; - } - }); - - async function createRepository( - onWrite?: () => void | Promise, - ): Promise { - adapter = new TestSqliteDatabase(); - const context = await adapter.connect(); - await adapter.exec(` - INSERT INTO users (id, username, password_hash) - VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash'); - INSERT INTO ssh_data (id, user_id, name, ip, port, username, auth_type) - VALUES (1, 'user-1', 'one', '10.0.0.1', 22, 'root', 'password'), (2, 'user-1', 'two', '10.0.0.2', 22, 'root', 'password'), (3, 'user-2', 'other', '10.0.0.3', 22, 'root', 'password'); - `); - - return new SessionRecordingRepository(context, onWrite); - } - - it("creates and lists session recordings with host metadata", async () => { - const repo = await createRepository(); - - const first = await repo.create({ - userId: "user-1", - hostId: 1, - startedAt: "2026-06-27T00:00:00.000Z", - endedAt: "2026-06-27T00:01:00.000Z", - duration: 60, - recordingPath: "/tmp/one.log", - }); - await repo.create({ - userId: "user-1", - hostId: 2, - startedAt: "2026-06-27T00:02:00.000Z", - recordingPath: "/tmp/two.log", - }); - await repo.create({ - userId: "user-2", - hostId: 3, - startedAt: "2026-06-27T00:03:00.000Z", - }); - - expect(first).toMatchObject({ - userId: "user-1", - hostId: 1, - duration: 60, - recordingPath: "/tmp/one.log", - }); - - const rows = await repo.listByUserIdWithHost("user-1"); - expect(rows.map((row) => row.hostName)).toEqual(["two", "one"]); - expect(rows[0]).toMatchObject({ - hostIp: "10.0.0.2", - recordingPath: "/tmp/two.log", - protocol: "ssh", - format: "text", - }); - }); - - it("finds paths and prunes old recordings", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - const old = await repo.create({ - userId: "user-1", - hostId: 1, - startedAt: "2026-01-01T00:00:00.000Z", - recordingPath: "/tmp/old.log", - }); - const current = await repo.create({ - userId: "user-1", - hostId: 1, - startedAt: "2026-06-27T00:00:00.000Z", - recordingPath: "/tmp/current.log", - }); - expect(writeCount).toBe(2); - - expect(await repo.findPathByIdForUser("user-2", old.id)).toBeNull(); - expect(await repo.findPathByIdForUser("user-1", old.id)).toMatchObject({ - recordingPath: "/tmp/old.log", - }); - expect(await repo.listPathsOlderThan("2026-02-01T00:00:00.000Z")).toEqual([ - { id: old.id, recordingPath: "/tmp/old.log" }, - ]); - - expect(await repo.deleteById(old.id)).toBe(true); - expect(await repo.deleteById(old.id)).toBe(false); - expect(await repo.findByIdForUser("user-1", current.id)).toMatchObject({ - id: current.id, - }); - expect(writeCount).toBe(3); - }); - - it("deletes recordings by user and host references", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - const first = await repo.create({ - userId: "user-1", - hostId: 1, - startedAt: "2026-06-27T00:00:00.000Z", - }); - await repo.create({ - userId: "user-1", - hostId: 2, - startedAt: "2026-06-27T00:01:00.000Z", - }); - await repo.create({ - userId: "user-2", - hostId: 3, - startedAt: "2026-06-27T00:02:00.000Z", - }); - expect(writeCount).toBe(3); - - expect(await repo.deleteForUser("user-2", first.id)).toBe(false); - expect(await repo.deleteForUser("user-1", first.id)).toBe(true); - expect(await repo.deleteByHostIds([])).toBe(0); - expect(await repo.deleteByHostIds([2])).toBe(1); - expect(writeCount).toBe(5); - - expect(await repo.deleteByUserId("user-2")).toBe(1); - expect(await repo.deleteByHostId(3)).toBe(0); - expect(writeCount).toBe(6); - }); -}); diff --git a/src/backend/tests/database/routes/session-log-routes.test.ts b/src/backend/tests/database/routes/session-log-routes.test.ts deleted file mode 100644 index 73dfe8845..000000000 --- a/src/backend/tests/database/routes/session-log-routes.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import path from "path"; - -// Stub db, logger, fs, and AuthManager before importing the route module -const mockSelect = vi.fn(); -const mockDelete = vi.fn(); -const mockInsert = vi.fn(); - -vi.mock("../../../database/db/index.js", () => ({ - db: { - select: mockSelect, - delete: mockDelete, - insert: mockInsert, - }, -})); - -vi.mock("../../../utils/logger.js", () => ({ - apiLogger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), success: vi.fn() }, -})); - -vi.mock("../../../utils/auth-manager.js", () => ({ - AuthManager: { - getInstance: () => ({ - createAuthMiddleware: - () => (_req: unknown, _res: unknown, next: () => void) => - next(), - }), - }, -})); - -// The route module calls PermissionManager.getInstance() at import time and -// pulls in the repository factory, which loads the drizzle schema and the -// better-sqlite3 native binding. Importing that tree costs seconds under a -// concurrent full run — enough to blow the 5s test timeout — and none of it is -// under test here. -vi.mock("../../../utils/permission-manager.js", () => ({ - PermissionManager: { - getInstance: () => ({ - canAccessHost: vi.fn(), - }), - }, -})); - -vi.mock("../../../database/repositories/factory.js", () => ({ - createCurrentSessionRecordingRepository: vi.fn(), - createCurrentSettingsRepository: vi.fn(), - getCurrentSettingValue: vi.fn(), -})); - -const mockReadFile = vi.fn(); -const mockStat = vi.fn(); -const mockUnlink = vi.fn(); -const mockExistsSync = vi.fn(); - -vi.mock("fs", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - promises: { readFile: mockReadFile, unlink: mockUnlink }, - existsSync: mockExistsSync, - statSync: mockStat, - }; -}); - -// Build a chainable drizzle-like query stub -function makeChain(resolveValue: unknown) { - const chain: Record = {}; - const methods = [ - "from", - "leftJoin", - "where", - "orderBy", - "limit", - "set", - "values", - ]; - for (const m of methods) { - chain[m] = vi.fn(() => chain); - } - (chain as unknown as Promise).then = (cb: (v: unknown) => unknown) => - Promise.resolve(resolveValue).then(cb); - (chain as unknown as Promise).catch = ( - cb: (e: unknown) => unknown, - ) => Promise.resolve(resolveValue).catch(cb); - return chain; -} - -describe("session-log-routes", () => { - beforeEach(() => { - vi.clearAllMocks(); - process.env.DATA_DIR = "/data"; - }); - - describe("GET / - list logs", () => { - it("returns logs for the authenticated user with file size", async () => { - const rows = [ - { - id: 1, - hostId: 10, - userId: "u1", - startedAt: "2026-01-01T00:00:00Z", - endedAt: "2026-01-01T00:05:00Z", - duration: 300, - recordingPath: "/data/session_logs/u1/abc.log", - hostName: "my-server", - hostIp: "10.0.0.1", - }, - ]; - const chain = makeChain(rows); - mockSelect.mockReturnValue(chain); - mockStat.mockReturnValue({ size: 4096 }); - - // Directly call the route handler extracted from the module - const { default: router } = - await import("../../../database/routes/session-log-routes.js"); - expect(router).toBeDefined(); - }); - }); - - describe("path traversal guard", () => { - it("rejects paths outside the allowed session_logs directory", () => { - const allowedBase = path.resolve("/data", "session_logs"); - const malicious = path.resolve("/data/session_logs/../../etc/passwd"); - expect(malicious.startsWith(allowedBase)).toBe(false); - }); - - it("allows a legitimate session log path", () => { - const allowedBase = path.resolve("/data", "session_logs"); - const valid = path.resolve("/data/session_logs/user1/abc.log"); - expect(valid.startsWith(allowedBase)).toBe(true); - }); - }); - - describe("formatters (pure logic)", () => { - it("stat returns size when file exists", () => { - mockExistsSync.mockReturnValue(true); - mockStat.mockReturnValue({ size: 1234 }); - const exists = mockExistsSync("/some/file.log"); - const { size } = mockStat("/some/file.log"); - expect(exists).toBe(true); - expect(size).toBe(1234); - }); - }); -}); diff --git a/src/backend/tests/utils/session-recording-settings-migration.test.ts b/src/backend/tests/utils/session-recording-settings-migration.test.ts new file mode 100644 index 000000000..733c691e2 --- /dev/null +++ b/src/backend/tests/utils/session-recording-settings-migration.test.ts @@ -0,0 +1,159 @@ +/** + * The session logging host-column migration. + * + * The legacy column defaulted to true, the plugin's enable switch defaults + * to false, so every host needs an explicit row, not just the ones that + * turned it off. Running it twice must not duplicate or clobber what it + * moved. + */ + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +interface HostRow { + id: number; + enable_session_logging: boolean | null; +} + +interface SettingsRow { + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; +} + +const hostRows: HostRow[] = []; +const settingsRows: SettingsRow[] = []; + +const find = ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, +) => + settingsRows.find( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId && + row.key === key, + ); + +const pluginSettingsRepository = { + get: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + ) => find(pluginId, scope, scopeId, key) ?? null, + set: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + value: string | null, + ) => { + const existing = find(pluginId, scope, scopeId, key); + if (existing) { + existing.value = value; + return; + } + settingsRows.push({ pluginId, scope, scopeId, key, value }); + }, +}; + +vi.mock("../../database/repositories/factory.js", () => ({ + createCurrentPluginSettingsRepository: () => pluginSettingsRepository, +})); + +vi.mock("../../database/db/index.js", () => ({ + getDb: () => ({ + all: async () => hostRows, + }), +})); + +const { runSessionRecordingSettingsMigration } = + await import("../../utils/crypto-migration/session-recording-settings-migration.js"); + +function storedValue(hostId: number, key: string): unknown { + const row = settingsRows.find( + (entry) => entry.scopeId === String(hostId) && entry.key === key, + ); + return row?.value === null || row?.value === undefined + ? undefined + : JSON.parse(row.value); +} + +beforeEach(() => { + hostRows.length = 0; + settingsRows.length = 0; +}); + +describe("runSessionRecordingSettingsMigration", () => { + it("moves an enabled host's switch into plugin settings", async () => { + hostRows.push({ id: 1, enable_session_logging: true }); + + const result = await runSessionRecordingSettingsMigration(); + + expect(result.moved).toBe(1); + expect(storedValue(1, "enableSessionRecording")).toBe(true); + expect( + settingsRows.every((row) => row.pluginId === "session-recording"), + ).toBe(true); + expect(settingsRows.every((row) => row.scope === "host")).toBe(true); + }); + + it("moves a disabled host's switch too, since the defaults flipped", async () => { + hostRows.push({ id: 3, enable_session_logging: false }); + + const result = await runSessionRecordingSettingsMigration(); + + expect(result.moved).toBe(1); + expect(storedValue(3, "enableSessionRecording")).toBe(false); + }); + + it("treats a null legacy value as on, matching the old column default", async () => { + hostRows.push({ id: 4, enable_session_logging: null }); + + const result = await runSessionRecordingSettingsMigration(); + + expect(result.moved).toBe(1); + expect(storedValue(4, "enableSessionRecording")).toBe(true); + }); + + it("does nothing on a fresh install", async () => { + const result = await runSessionRecordingSettingsMigration(); + + expect(result.moved).toBe(0); + expect(settingsRows).toEqual([]); + }); + + it("is idempotent: a second run changes nothing", async () => { + hostRows.push({ id: 2, enable_session_logging: true }); + + await runSessionRecordingSettingsMigration(); + const afterFirst = JSON.parse(JSON.stringify(settingsRows)); + + const second = await runSessionRecordingSettingsMigration(); + + expect(second.moved).toBe(0); + expect(second.skipped).toBe(1); + expect(settingsRows).toEqual(afterFirst); + }); + + it("does not overwrite a value the plugin already saved", async () => { + hostRows.push({ id: 5, enable_session_logging: true }); + await pluginSettingsRepository.set( + "session-recording", + "host", + "5", + "enableSessionRecording", + "false", + ); + + const result = await runSessionRecordingSettingsMigration(); + + expect(result.skipped).toBe(1); + expect(storedValue(5, "enableSessionRecording")).toBe(false); + }); +}); diff --git a/src/backend/utils/crypto-migration/session-recording-settings-migration.ts b/src/backend/utils/crypto-migration/session-recording-settings-migration.ts new file mode 100644 index 000000000..e25a0b3d7 --- /dev/null +++ b/src/backend/utils/crypto-migration/session-recording-settings-migration.ts @@ -0,0 +1,89 @@ +/** + * Moves the session logging host column into the session-recording plugin's + * host-scope settings, before the column is dropped from ssh_data. + * + * The legacy column defaulted to true; the plugin's enable switch defaults to + * false like every other plugin's. So unlike a migration whose old and new + * defaults agree, every host needs an explicit row here, not just the ones + * that turned the feature off. + * + * Idempotent: skips a host that already has a session-recording settings row, + * so it is safe to run on every boot. + */ + +import { sql } from "drizzle-orm"; +import { databaseLogger } from "../logger.js"; +import { getDb } from "../../database/db/index.js"; +import { createCurrentPluginSettingsRepository } from "../../database/repositories/factory.js"; + +export interface SessionRecordingSettingsMigrationResult { + moved: number; + skipped: number; +} + +interface LegacySessionLoggingRow { + id: number; + enable_session_logging: number | boolean | null; +} + +export async function runSessionRecordingSettingsMigration(): Promise { + const result: SessionRecordingSettingsMigrationResult = { + moved: 0, + skipped: 0, + }; + + try { + const drizzleDb = getDb(); + // Raw SQL, not the typed schema, so this keeps working once schema.ts + // stops declaring this column. + const rows = await drizzleDb.all(sql` + SELECT id, enable_session_logging FROM ssh_data + `); + + if (rows.length === 0) return result; + + const pluginSettingsRepository = createCurrentPluginSettingsRepository(); + + for (const row of rows) { + const scopeId = String(row.id); + const existing = await pluginSettingsRepository.get( + "session-recording", + "host", + scopeId, + "enableSessionRecording", + ); + if (existing && existing.value !== null) { + result.skipped++; + continue; + } + + await pluginSettingsRepository.set( + "session-recording", + "host", + scopeId, + "enableSessionRecording", + JSON.stringify(row.enable_session_logging !== false), + ); + result.moved++; + } + + if (result.moved > 0) { + databaseLogger.info( + `Moved session recording settings for ${result.moved} host(s) into plugin settings`, + { + operation: "session_recording_settings_migration", + moved: result.moved, + }, + ); + } + } catch (error) { + // A failed migration must not stop the backend. Hosts simply keep the + // plugin's own default (off) until this runs successfully. + databaseLogger.warn("Session recording settings migration failed", { + operation: "session_recording_settings_migration", + error: error instanceof Error ? error.message : String(error), + }); + } + + return result; +} diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index c0804f124..c1303c27c 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -86,11 +86,6 @@ const MacrosPanel = lazy(() => import("@/sidebar/MacrosPanel").then((m) => ({ default: m.MacrosPanel })), ); -const SessionLogsPanel = lazy(() => - import("@/sidebar/SessionLogsPanel").then((m) => ({ - default: m.SessionLogsPanel, - })), -); // One screen behind both rail entries. It lazy-loads the profile and admin // panels itself, so the shell no longer imports either directly. const SettingsScreen = lazy(() => @@ -2525,12 +2520,6 @@ export function AppShell({ )} - {railView === "session-logs" && ( -
- -
- )} - {(railView === "user-profile" || (railView === "admin-settings" && showAdminUI)) && (
diff --git a/src/ui/api/session-log-api.ts b/src/ui/api/session-log-api.ts deleted file mode 100644 index 25e696a33..000000000 --- a/src/ui/api/session-log-api.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { authApi, handleApiError } from "@/main-axios"; - -export type SessionLogRecord = { - id: number; - hostId: number; - userId: string; - startedAt: string; - endedAt: string | null; - duration: number | null; - recordingPath: string | null; - hostName: string | null; - hostIp: string | null; - sizeBytes: number | null; - protocol: "ssh" | "rdp" | "vnc" | "telnet"; - format: "text" | "asciicast" | "guacamole"; - username: string | null; -}; - -export async function getSessionLogs(): Promise { - try { - const response = await authApi.get("/session_logs/"); - return response.data.logs; - } catch (error) { - throw handleApiError(error, "fetch session logs"); - } -} - -export async function getSessionLogBlob(id: number): Promise { - try { - const response = await authApi.get(`/session_logs/${id}/content`, { - responseType: "blob", - }); - return response.data; - } catch (error) { - throw handleApiError(error, "fetch session recording"); - } -} - -export async function getSessionRecordingRetention(): Promise { - try { - const response = await authApi.get("/session_logs/retention"); - return response.data.retentionDays; - } catch (error) { - throw handleApiError(error, "fetch session recording retention"); - } -} - -export async function setSessionRecordingRetention( - retentionDays: number, -): Promise { - try { - await authApi.put("/session_logs/retention", { retentionDays }); - } catch (error) { - throw handleApiError(error, "update session recording retention"); - } -} - -export async function getSessionLogContent(id: number): Promise { - try { - const response = await authApi.get(`/session_logs/${id}/content`, { - responseType: "text", - }); - return response.data; - } catch (error) { - throw handleApiError(error, "fetch session log content"); - } -} - -export async function deleteSessionLog(id: number): Promise { - try { - await authApi.delete(`/session_logs/${id}`); - } catch (error) { - throw handleApiError(error, "delete session log"); - } -} diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index ef3010620..45c6185c1 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -692,7 +692,6 @@ "quickConnect": "Quick Connect", "sshTools": "SSH Tools", "history": "History", - "sessionLogs": "Session Logs", "openAsTabHint": "Ctrl+click or middle click to open as a tab", "openAsTab": "Open as tab", "openInRightDock": "Open in right dock", @@ -1029,8 +1028,6 @@ "enableAutoMoshDesc": "Prefer Mosh over SSH if available", "enableAutoTmux": "Enable Auto-Tmux", "enableAutoTmuxDesc": "Keep your shell and anything running in it alive across disconnects and browser restarts by running inside tmux (tmux must be installed on the host)", - "enableSessionLogging": "Session Logging", - "enableSessionLoggingDesc": "Record terminal session output for later review", "localEchoLabel": "Local Echo", "localEchoDesc": "Predict safe printable keystrokes locally on slow connections. Password prompts and control input are never echoed.", "localEchoDefault": "Use global setting", @@ -1979,23 +1976,6 @@ "disk": "Disk", "viewServerDetails": "View server details" }, - "sessionLogs": { - "title": "Session Logs", - "noLogs": "No session logs yet", - "noLogsDesc": "Enable session logging on a host to start recording", - "duration": "Duration", - "viewLog": "View log", - "downloadLog": "Download", - "downloadAsText": "Download as text", - "deleteLog": "Delete", - "confirmDelete": "Delete this session log?", - "confirmDeleteDesc": "This action cannot be undone.", - "copyContent": "Copy", - "copied": "Copied!", - "loadError": "Failed to load session logs", - "deleteError": "Failed to delete session log", - "filterByHost": "Filter by host..." - }, "admin": { "sectionGeneral": "General", "sectionOidc": "OIDC", @@ -3151,6 +3131,10 @@ "title": "Remember small settings", "consequence": "Kept separate from other plugins." }, + "files:own": { + "title": "Store its own files", + "consequence": "In its own folder on disk, separate from other plugins." + }, "secrets:own": { "title": "Store its own secrets", "consequence": "Encrypted by Termix. The plugin never holds the key." diff --git a/src/ui/locales/translated/af_ZA.json b/src/ui/locales/translated/af_ZA.json index acd30d0ca..76fb04853 100644 --- a/src/ui/locales/translated/af_ZA.json +++ b/src/ui/locales/translated/af_ZA.json @@ -695,7 +695,6 @@ "quickConnect": "Vinnige verbinding", "sshTools": "SSH-gereedskap", "history": "Geskiedenis", - "sessionLogs": "Sessielogboeke", "openAsTabHint": "Ctrl+klik of middelklik om as 'n oortjie oop te maak", "openAsTab": "Maak oop as oortjie", "openInRightDock": "Maak oop in regterdok", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Verkies Mosh bo SSH indien beskikbaar", "enableAutoTmux": "Aktiveer Auto-Tmux", "enableAutoTmuxDesc": "Hou jou dop en enigiets wat daarin loop lewendig oor ontkoppelings en herbegin van blaaiers deur dit binne tmux te laat loop (tmux moet op die gasheer geïnstalleer wees)", - "enableSessionLogging": "Sessie-logging", - "enableSessionLoggingDesc": "Neem terminaalsessie-uitvoer op vir latere hersiening", "localEchoLabel": "Plaaslike Eggo", "localEchoDesc": "Voorspel veilige drukbare toetsaanslagen plaaslik op stadige verbindings. Wagwoordaanwysings en beheerinvoer word nooit herhaal nie.", "localEchoDefault": "Gebruik globale instelling", @@ -1969,23 +1966,6 @@ "disk": "Skyf", "viewServerDetails": "Bekyk bedienerbesonderhede" }, - "sessionLogs": { - "title": "Sessielogboeke", - "noLogs": "Geen sessielogboeke nog nie", - "noLogsDesc": "Aktiveer sessie-aanmelding op 'n gasheer om opname te begin", - "duration": "Duur", - "viewLog": "Bekyk logboek", - "downloadLog": "Laai af", - "downloadAsText": "Laai af as teks", - "deleteLog": "Vee uit", - "confirmDelete": "Vee hierdie sessielog uit?", - "confirmDeleteDesc": "Hierdie aksie kan nie ongedaan gemaak word nie.", - "copyContent": "Kopieer", - "copied": "Gekopieer!", - "loadError": "Kon nie sessielogboeke laai nie", - "deleteError": "Kon nie sessielogboek uitvee nie", - "filterByHost": "Filtreer volgens gasheer..." - }, "admin": { "sectionGeneral": "Algemeen", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/ar_SA.json b/src/ui/locales/translated/ar_SA.json index 0ae5d45e5..d297ba494 100644 --- a/src/ui/locales/translated/ar_SA.json +++ b/src/ui/locales/translated/ar_SA.json @@ -695,7 +695,6 @@ "quickConnect": "اتصال سريع", "sshTools": "أدوات SSH", "history": "السجل", - "sessionLogs": "سجلات الجلسات", "openAsTabHint": "اضغط على مفتاح Ctrl + انقر أو انقر بالزر الأوسط لفتحها كعلامة تبويب", "openAsTab": "افتح كعلامة تبويب", "openInRightDock": "افتح في الرصيف الأيمن", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "تفضيل Mosh على SSH إذا كان متاحاً", "enableAutoTmux": "تمكين tmux التلقائي", "enableAutoTmuxDesc": "حافظ على تشغيل shell وأي شيء يعمل بداخله أثناء انقطاع الاتصال وإعادة تشغيل المتصفح عن طريق التشغيل داخل tmux (يجب تثبيت tmux على المضيف).", - "enableSessionLogging": "تسجيل الجلسات", - "enableSessionLoggingDesc": "تسجيل مخرجات الجلسة الطرفية للمراجعة لاحقاً", "localEchoLabel": "صدى محلي", "localEchoDesc": "توقع ضغطات المفاتيح الآمنة القابلة للطباعة محليًا على الاتصالات البطيئة. لا يتم عرض مطالبات كلمة المرور أو مدخلات التحكم على الشاشة.", "localEchoDefault": "استخدم الإعدادات العامة", @@ -1969,23 +1966,6 @@ "disk": "القرص", "viewServerDetails": "عرض تفاصيل الخادم" }, - "sessionLogs": { - "title": "سجلات الجلسات", - "noLogs": "لا توجد سجلات جلسات بعد", - "noLogsDesc": "فعّل تسجيل الجلسات على مضيف لبدء التسجيل", - "duration": "المدة", - "viewLog": "عرض السجل", - "downloadLog": "تنزيل", - "downloadAsText": "تنزيل كنص", - "deleteLog": "حذف", - "confirmDelete": "حذف سجل الجلسة هذا؟", - "confirmDeleteDesc": "لا يمكن التراجع عن هذا الإجراء.", - "copyContent": "نسخ", - "copied": "تم النسخ!", - "loadError": "فشل في تحميل سجلات الجلسات", - "deleteError": "فشل في حذف سجل الجلسة", - "filterByHost": "تصفية حسب المضيف..." - }, "admin": { "sectionGeneral": "عام", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/bg_BG.json b/src/ui/locales/translated/bg_BG.json index cddba0b38..51f4fc0bb 100644 --- a/src/ui/locales/translated/bg_BG.json +++ b/src/ui/locales/translated/bg_BG.json @@ -695,7 +695,6 @@ "quickConnect": "Бързо свързване", "sshTools": "SSH инструменти", "history": "История", - "sessionLogs": "Дневници на сесиите", "openAsTabHint": "Ctrl+щракване или щракване със среден бутон, за да отворите като раздел", "openAsTab": "Отвори като раздел", "openInRightDock": "Отваряне в десния док", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Предпочитайте Mosh пред SSH, ако е наличен", "enableAutoTmux": "Активиране на Auto-Tmux", "enableAutoTmuxDesc": "Поддържайте вашата обвивка и всичко, което се изпълнява в нея, активно при прекъсвания на връзката и рестартиране на браузъра, като я стартирате вътре в tmux (tmux трябва да е инсталиран на хоста)", - "enableSessionLogging": "Записване на сесии", - "enableSessionLoggingDesc": "Записване на изхода от терминалната сесия за по-късен преглед", "localEchoLabel": "Локално ехо", "localEchoDesc": "Предсказвайте безопасни за печат натискания на клавиши локално при бавни връзки. Подканите за парола и контролните въведени команди никога не се повтарят.", "localEchoDefault": "Използвайте глобална настройка", @@ -1969,23 +1966,6 @@ "disk": "Диск", "viewServerDetails": "Преглед на подробности за сървъра" }, - "sessionLogs": { - "title": "Дневници на сесии", - "noLogs": "Все още няма логове на сесии", - "noLogsDesc": "Активирайте регистрирането на сесията на хост, за да започнете записа", - "duration": "Продължителност", - "viewLog": "Преглед на дневника", - "downloadLog": "Изтегляне", - "downloadAsText": "Изтегляне като текст", - "deleteLog": "Изтриване", - "confirmDelete": "Да се изтрие ли този дневник на сесията?", - "confirmDeleteDesc": "Това действие не може да бъде отменено.", - "copyContent": "Копиране", - "copied": "Копирано!", - "loadError": "Зареждането на лог файловете на сесията не бе успешно", - "deleteError": "Неуспешно изтриване на дневника на сесията", - "filterByHost": "Филтриране по хост..." - }, "admin": { "sectionGeneral": "Общи", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/bn_BD.json b/src/ui/locales/translated/bn_BD.json index 076564d46..5a7fa2cdb 100644 --- a/src/ui/locales/translated/bn_BD.json +++ b/src/ui/locales/translated/bn_BD.json @@ -695,7 +695,6 @@ "quickConnect": "দ্রুত সংযোগ", "sshTools": "SSH টুলস", "history": "ইতিহাস", - "sessionLogs": "সেশন লগ", "openAsTabHint": "ট্যাব হিসেবে খুলতে Ctrl+ক্লিক অথবা মিডল ক্লিক করুন।", "openAsTab": "ট্যাব হিসাবে খুলুন", "openInRightDock": "ডান ডকে খুলুন", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "উপলব্ধ থাকলে SSH-এর চেয়ে Mosh ব্যবহার করা শ্রেয়।", "enableAutoTmux": "অটো-Tmux সক্রিয় করুন", "enableAutoTmuxDesc": "সংযোগ বিচ্ছিন্ন হওয়া এবং ব্রাউজার পুনরায় চালু হওয়ার পরেও আপনার শেল এবং এর মধ্যে চলমান সবকিছু সচল রাখতে tmux-এর ভিতরে এটি চালান (tmux অবশ্যই হোস্টে ইনস্টল করা থাকতে হবে)।", - "enableSessionLogging": "সেশন লগিং", - "enableSessionLoggingDesc": "পরবর্তী পর্যালোচনার জন্য টার্মিনাল সেশনের আউটপুট রেকর্ড করুন।", "localEchoLabel": "স্থানীয় প্রতিধ্বনি", "localEchoDesc": "ধীরগতির সংযোগে স্থানীয়ভাবে নিরাপদ ও মুদ্রণযোগ্য কীস্ট্রোক অনুমান করুন। পাসওয়ার্ড প্রম্পট এবং কন্ট্রোল ইনপুট কখনও প্রতিধ্বনিত হয় না।", "localEchoDefault": "গ্লোবাল সেটিং ব্যবহার করুন", @@ -1969,23 +1966,6 @@ "disk": "ডিস্ক", "viewServerDetails": "সার্ভারের বিবরণ দেখুন" }, - "sessionLogs": { - "title": "সেশন লগ", - "noLogs": "এখনো কোনো সেশন লগ নেই", - "noLogsDesc": "রেকর্ডিং শুরু করতে কোনো হোস্টে সেশন লগিং চালু করুন।", - "duration": "সময়কাল", - "viewLog": "লগ দেখুন", - "downloadLog": "ডাউনলোড করুন", - "downloadAsText": "টেক্সট হিসেবে ডাউনলোড করুন", - "deleteLog": "মুছে ফেলুন", - "confirmDelete": "এই সেশন লগটি মুছে ফেলবেন?", - "confirmDeleteDesc": "এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।", - "copyContent": "অনুলিপি", - "copied": "অনুলিপি করা হয়েছে!", - "loadError": "সেশন লগ লোড করতে ব্যর্থ হয়েছে", - "deleteError": "সেশন লগ মুছে ফেলতে ব্যর্থ হয়েছে", - "filterByHost": "হোস্ট দ্বারা ফিল্টার করুন..." - }, "admin": { "sectionGeneral": "সাধারণ", "sectionOidc": "ওআইডিসি", diff --git a/src/ui/locales/translated/ca_ES.json b/src/ui/locales/translated/ca_ES.json index 0f7e811c2..9489f531a 100644 --- a/src/ui/locales/translated/ca_ES.json +++ b/src/ui/locales/translated/ca_ES.json @@ -695,7 +695,6 @@ "quickConnect": "Connexió ràpida", "sshTools": "Eines SSH", "history": "Història", - "sessionLogs": "Registres de sessió", "openAsTabHint": "Ctrl+clic o clic amb el botó del mig per obrir com a pestanya", "openAsTab": "Obre com a pestanya", "openInRightDock": "Obre al moll dret", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Prefereix Mosh a SSH si està disponible", "enableAutoTmux": "Activa la transmissió automàtica", "enableAutoTmuxDesc": "Mantingueu actiu el vostre shell i tot el que s'hi executi després de desconnexions i reinicis del navegador executant-lo dins de tmux (tmux ha d'estar instal·lat a l'amfitrió)", - "enableSessionLogging": "Registre de sessions", - "enableSessionLoggingDesc": "Enregistra la sortida de la sessió de terminal per a una revisió posterior", "localEchoLabel": "Eco local", "localEchoDesc": "Prediu localment les pulsacions de tecles imprimibles de manera segura en connexions lentes. Les sol·licituds de contrasenya i l'entrada de control no es reprodueixen mai.", "localEchoDefault": "Utilitza la configuració global", @@ -1969,23 +1966,6 @@ "disk": "Disc", "viewServerDetails": "Veure els detalls del servidor" }, - "sessionLogs": { - "title": "Registres de sessió", - "noLogs": "Encara no hi ha registres de sessió", - "noLogsDesc": "Habilita el registre de sessions en un amfitrió per iniciar l'enregistrament", - "duration": "Durada", - "viewLog": "Veure registre", - "downloadLog": "Descarrega", - "downloadAsText": "Descarrega com a text", - "deleteLog": "Suprimeix", - "confirmDelete": "Voleu suprimir aquest registre de sessió?", - "confirmDeleteDesc": "Aquesta acció no es pot desfer.", - "copyContent": "Còpia", - "copied": "Copiat!", - "loadError": "No s'han pogut carregar els registres de sessió", - "deleteError": "No s'ha pogut suprimir el registre de sessió", - "filterByHost": "Filtra per amfitrió..." - }, "admin": { "sectionGeneral": "General", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/cs_CZ.json b/src/ui/locales/translated/cs_CZ.json index a0ce7d92a..9e2401fa9 100644 --- a/src/ui/locales/translated/cs_CZ.json +++ b/src/ui/locales/translated/cs_CZ.json @@ -695,7 +695,6 @@ "quickConnect": "Rychlé připojení", "sshTools": "SSH nástroje", "history": "Dějiny", - "sessionLogs": "Protokoly relací", "openAsTabHint": "Ctrl+kliknutí nebo prostřední kliknutí pro otevření jako záložky", "openAsTab": "Otevřít jako kartu", "openInRightDock": "Otevřít v pravém doku", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Pokud je k dispozici, preferujte Mosh před SSH.", "enableAutoTmux": "Povolit Auto-Tmux", "enableAutoTmuxDesc": "Udržujte svůj shell a vše, co v něm běží, aktivní i po odpojení a restartu prohlížeče spuštěním uvnitř tmuxu (tmux musí být nainstalován na hostiteli).", - "enableSessionLogging": "Protokolování relací", - "enableSessionLoggingDesc": "Zaznamenejte výstup terminálové relace pro pozdější kontrolu", "localEchoLabel": "Místní ozvěna", "localEchoDesc": "Předvídat bezpečně tisknutelné stisky kláves lokálně na pomalém připojení. Výzvy k zadání hesla a ovládací prvky se nikdy nezobrazují.", "localEchoDefault": "Použít globální nastavení", @@ -1969,23 +1966,6 @@ "disk": "Disk", "viewServerDetails": "Zobrazit podrobnosti o serveru" }, - "sessionLogs": { - "title": "Protokoly relací", - "noLogs": "Zatím žádné protokoly relací", - "noLogsDesc": "Povolte protokolování relace na hostiteli pro zahájení nahrávání", - "duration": "Trvání", - "viewLog": "Zobrazit protokol", - "downloadLog": "Stáhnout", - "downloadAsText": "Stáhnout jako text", - "deleteLog": "Vymazat", - "confirmDelete": "Smazat tento protokol relace?", - "confirmDeleteDesc": "Tuto akci nelze vrátit zpět.", - "copyContent": "Kopie", - "copied": "Zkopírováno!", - "loadError": "Nepodařilo se načíst protokoly relací", - "deleteError": "Nepodařilo se smazat protokol relace", - "filterByHost": "Filtrovat podle hostitele..." - }, "admin": { "sectionGeneral": "Generál", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/da_DK.json b/src/ui/locales/translated/da_DK.json index dcef83694..2311472f6 100644 --- a/src/ui/locales/translated/da_DK.json +++ b/src/ui/locales/translated/da_DK.json @@ -695,7 +695,6 @@ "quickConnect": "Hurtig forbindelse", "sshTools": "SSH-værktøjer", "history": "Historie", - "sessionLogs": "Sessionslogfiler", "openAsTabHint": "Ctrl+klik eller midterste klik for at åbne som faneblad", "openAsTab": "Åbn som faneblad", "openInRightDock": "Åbn i højre dock", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Foretræk Mosh frem for SSH, hvis det er muligt", "enableAutoTmux": "Aktivér Auto-Tmux", "enableAutoTmuxDesc": "Hold din shell og alt, der kører i den, i live på tværs af afbrydelser og genstart af browseren ved at køre den inde i tmux (tmux skal være installeret på værten)", - "enableSessionLogging": "Sessionslogning", - "enableSessionLoggingDesc": "Optag output fra terminalsessionen til senere gennemgang", "localEchoLabel": "Lokalt ekko", "localEchoDesc": "Forudsig sikre, udskrivbare tastetryk lokalt på langsomme forbindelser. Adgangskodeprompter og kontrolinput gentages aldrig.", "localEchoDefault": "Brug global indstilling", @@ -1969,23 +1966,6 @@ "disk": "Disk", "viewServerDetails": "Se serveroplysninger" }, - "sessionLogs": { - "title": "Sessionslogfiler", - "noLogs": "Ingen sessionslogfiler endnu", - "noLogsDesc": "Aktivér sessionslogning på en vært for at starte optagelse", - "duration": "Varighed", - "viewLog": "Se log", - "downloadLog": "Download", - "downloadAsText": "Download som tekst", - "deleteLog": "Slet", - "confirmDelete": "Slet denne sessionslog?", - "confirmDeleteDesc": "Denne handling kan ikke fortrydes.", - "copyContent": "Kopi", - "copied": "Kopieret!", - "loadError": "Kunne ikke indlæse sessionslogfiler", - "deleteError": "Kunne ikke slette sessionsloggen", - "filterByHost": "Filtrer efter vært..." - }, "admin": { "sectionGeneral": "Generel", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/de_DE.json b/src/ui/locales/translated/de_DE.json index 2bfb35ff9..37dc7a794 100644 --- a/src/ui/locales/translated/de_DE.json +++ b/src/ui/locales/translated/de_DE.json @@ -695,7 +695,6 @@ "quickConnect": "Schnellverbindung", "sshTools": "SSH-Tools", "history": "Verlauf", - "sessionLogs": "Sitzungsprotokolle", "openAsTabHint": "Strg+Klick oder mittlerer Klick zum Öffnen als Tab", "openAsTab": "Als Tab öffnen", "openInRightDock": "Im rechten Dock öffnen", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Mosh gegenüber SSH bevorzugen, falls verfügbar", "enableAutoTmux": "Auto-Tmux aktivieren", "enableAutoTmuxDesc": "Halten Sie Ihre Shell und alle darin laufenden Prozesse auch bei Verbindungsabbrüchen und Browserneustarts am Laufen, indem Sie den Befehl innerhalb von tmux ausführen (tmux muss auf dem Host installiert sein).", - "enableSessionLogging": "Sitzungsprotokollierung", - "enableSessionLoggingDesc": "Terminal-Sitzungsausgabe zur späteren Überprüfung aufzeichnen", "localEchoLabel": "Lokales Echo", "localEchoDesc": "Bei langsamen Verbindungen werden sichere, druckbare Tastatureingaben lokal vorhergesagt. Passwortabfragen und Steuereingaben werden niemals wiederholt.", "localEchoDefault": "Globale Einstellung verwenden", @@ -1969,23 +1966,6 @@ "disk": "Datenträger", "viewServerDetails": "Serverdetails anzeigen" }, - "sessionLogs": { - "title": "Sitzungsprotokolle", - "noLogs": "Noch keine Sitzungsprotokolle", - "noLogsDesc": "Aktivieren Sie die Sitzungsprotokollierung auf einem Host, um die Aufzeichnung zu starten", - "duration": "Dauer", - "viewLog": "Protokoll anzeigen", - "downloadLog": "Herunterladen", - "downloadAsText": "Als Text herunterladen", - "deleteLog": "Löschen", - "confirmDelete": "Dieses Sitzungsprotokoll löschen?", - "confirmDeleteDesc": "Diese Aktion kann nicht rückgängig gemacht werden.", - "copyContent": "Kopieren", - "copied": "Kopiert!", - "loadError": "Fehler beim Laden der Sitzungsprotokolle", - "deleteError": "Fehler beim Löschen des Sitzungsprotokolls", - "filterByHost": "Nach Host filtern..." - }, "admin": { "sectionGeneral": "Allgemein", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/el_GR.json b/src/ui/locales/translated/el_GR.json index 3ba876577..c9b34af7e 100644 --- a/src/ui/locales/translated/el_GR.json +++ b/src/ui/locales/translated/el_GR.json @@ -695,7 +695,6 @@ "quickConnect": "Γρήγορη σύνδεση", "sshTools": "Εργαλεία SSH", "history": "Ιστορία", - "sessionLogs": "Αρχεία καταγραφής συνεδρίας", "openAsTabHint": "Ctrl+κλικ ή μεσαίο κλικ για άνοιγμα ως καρτέλα", "openAsTab": "Άνοιγμα ως καρτέλα", "openInRightDock": "Άνοιγμα στη δεξιά βάση σύνδεσης", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Προτιμήστε το Mosh αντί για SSH, εάν είναι διαθέσιμο", "enableAutoTmux": "Ενεργοποίηση Auto-Tmux", "enableAutoTmuxDesc": "Διατηρήστε το κέλυφος (shell) και οτιδήποτε εκτελείται σε αυτό ενεργό σε αποσυνδέσεις και επανεκκινήσεις του προγράμματος περιήγησης, εκτελώντας το μέσα στο tmux (το tmux πρέπει να είναι εγκατεστημένο στον κεντρικό υπολογιστή)", - "enableSessionLogging": "Καταγραφή συνεδρίας", - "enableSessionLoggingDesc": "Καταγραφή εξόδου συνεδρίας τερματικού για μελλοντική αναθεώρηση", "localEchoLabel": "Τοπική Ηχώ", "localEchoDesc": "Προβλέψτε ασφαλείς εκτυπώσιμες πληκτρολογήσεις τοπικά σε αργές συνδέσεις. Οι προτροπές κωδικού πρόσβασης και η εισαγωγή ελέγχου δεν επαναλαμβάνονται ποτέ.", "localEchoDefault": "Χρήση καθολικής ρύθμισης", @@ -1969,23 +1966,6 @@ "disk": "Δίσκος", "viewServerDetails": "Προβολή λεπτομερειών διακομιστή" }, - "sessionLogs": { - "title": "Αρχεία καταγραφής συνεδρίας", - "noLogs": "Δεν υπάρχουν ακόμη αρχεία καταγραφής συνεδρίας", - "noLogsDesc": "Ενεργοποίηση καταγραφής περιόδου λειτουργίας σε έναν κεντρικό υπολογιστή για έναρξη εγγραφής", - "duration": "Διάρκεια", - "viewLog": "Προβολή αρχείου καταγραφής", - "downloadLog": "Λήψη", - "downloadAsText": "Λήψη ως κείμενο", - "deleteLog": "Διαγράφω", - "confirmDelete": "Διαγραφή αυτού του αρχείου καταγραφής περιόδου σύνδεσης;", - "confirmDeleteDesc": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", - "copyContent": "Αντίγραφο", - "copied": "Αντιγράφηκε!", - "loadError": "Αποτυχία φόρτωσης αρχείων καταγραφής περιόδου λειτουργίας", - "deleteError": "Η διαγραφή του αρχείου καταγραφής περιόδου λειτουργίας απέτυχε", - "filterByHost": "Φιλτράρισμα κατά κεντρικό υπολογιστή..." - }, "admin": { "sectionGeneral": "Γενικός", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/es_ES.json b/src/ui/locales/translated/es_ES.json index 96b8793ff..fb261abbd 100644 --- a/src/ui/locales/translated/es_ES.json +++ b/src/ui/locales/translated/es_ES.json @@ -695,7 +695,6 @@ "quickConnect": "Conexión rápida", "sshTools": "Herramientas SSH", "history": "Historial", - "sessionLogs": "Registros de sesión", "openAsTabHint": "Ctrl+clic o clic central para abrir como pestaña", "openAsTab": "Abrir como pestaña", "openInRightDock": "Abrir en el muelle derecho", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Preferir Mosh sobre SSH si está disponible", "enableAutoTmux": "Habilitar Auto-Tmux", "enableAutoTmuxDesc": "Mantén tu shell y todo lo que se esté ejecutando en él activo durante desconexiones y reinicios del navegador ejecutándolo dentro de tmux (tmux debe estar instalado en el host).", - "enableSessionLogging": "Registro de sesión", - "enableSessionLoggingDesc": "Registrar la salida de la sesión de terminal para su revisión posterior", "localEchoLabel": "Eco local", "localEchoDesc": "Predice localmente las pulsaciones de teclas seguras para imprimir en conexiones lentas. Las solicitudes de contraseña y la entrada de control nunca se repiten.", "localEchoDefault": "Utilizar la configuración global", @@ -1969,23 +1966,6 @@ "disk": "Disco", "viewServerDetails": "Ver detalles del servidor" }, - "sessionLogs": { - "title": "Registros de sesión", - "noLogs": "Aún no hay registros de sesión", - "noLogsDesc": "Activa el registro de sesiones en un host para empezar a grabar", - "duration": "Duración", - "viewLog": "Ver registro", - "downloadLog": "Descargar", - "downloadAsText": "Descargar como texto", - "deleteLog": "Eliminar", - "confirmDelete": "¿Eliminar este registro de sesión?", - "confirmDeleteDesc": "Esta acción no se puede deshacer.", - "copyContent": "Copiar", - "copied": "¡Copiado!", - "loadError": "Error al cargar los registros de sesión", - "deleteError": "Error al eliminar el registro de sesión", - "filterByHost": "Filtrar por host..." - }, "admin": { "sectionGeneral": "General", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/fi_FI.json b/src/ui/locales/translated/fi_FI.json index ce07ef8e0..66f9c0a98 100644 --- a/src/ui/locales/translated/fi_FI.json +++ b/src/ui/locales/translated/fi_FI.json @@ -695,7 +695,6 @@ "quickConnect": "Pikayhteys", "sshTools": "SSH-työkalut", "history": "Historia", - "sessionLogs": "Istuntolokit", "openAsTabHint": "Ctrl+napsauta tai keskinäppäinpainike avaa välilehdenä", "openAsTab": "Avaa välilehtenä", "openInRightDock": "Avaa oikeanpuoleisessa telakassa", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Suosi Moshia SSH:n sijaan, jos saatavilla", "enableAutoTmux": "Ota automaattinen Tmux käyttöön", "enableAutoTmuxDesc": "Pidä komentotulkkisi ja kaikki siinä käynnissä olevat toiminnot toiminnassa yhteyskatkosten ja selaimen uudelleenkäynnistysten aikana suorittamalla se tmuxin sisällä (tmuxin on oltava asennettuna isäntäkoneelle)", - "enableSessionLogging": "Istuntojen lokitiedot", - "enableSessionLoggingDesc": "Tallenna pääteistunnon tuloste myöhempää tarkastelua varten", "localEchoLabel": "Paikallinen kaiku", "localEchoDesc": "Ennusta turvalliset tulostettavat näppäinpainallukset paikallisesti hitailla yhteyksillä. Salasanakehotteita ja ohjaussyötettä ei koskaan toisteta.", "localEchoDefault": "Käytä yleistä asetusta", @@ -1969,23 +1966,6 @@ "disk": "Levy", "viewServerDetails": "Näytä palvelimen tiedot" }, - "sessionLogs": { - "title": "Istuntolokit", - "noLogs": "Ei vielä istuntolokeja", - "noLogsDesc": "Ota istunnon lokikirjaus käyttöön isännässä aloittaaksesi tallennuksen", - "duration": "Kesto", - "viewLog": "Näytä loki", - "downloadLog": "Lataa", - "downloadAsText": "Lataa tekstinä", - "deleteLog": "Poistaa", - "confirmDelete": "Poistetaanko tämä istuntoloki?", - "confirmDeleteDesc": "Tätä toimintoa ei voi perua.", - "copyContent": "Kopioida", - "copied": "Kopioitu!", - "loadError": "Istuntolokien lataaminen epäonnistui", - "deleteError": "Istuntolokitietojen poistaminen epäonnistui", - "filterByHost": "Suodata isännän mukaan..." - }, "admin": { "sectionGeneral": "Yleistä", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/fr_FR.json b/src/ui/locales/translated/fr_FR.json index a4111399f..0ed88d4b3 100644 --- a/src/ui/locales/translated/fr_FR.json +++ b/src/ui/locales/translated/fr_FR.json @@ -695,7 +695,6 @@ "quickConnect": "Connexion rapide", "sshTools": "Outils SSH", "history": "Historique", - "sessionLogs": "Journaux de session", "openAsTabHint": "Ctrl+clic ou clic du milieu pour ouvrir dans un onglet", "openAsTab": "Ouvrir comme onglet", "openInRightDock": "Ouvrir dans le dock de droite", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Préférer Mosh à SSH si disponible", "enableAutoTmux": "Activer Auto-Tmux", "enableAutoTmuxDesc": "Pour que votre shell et tout ce qui y est exécuté restent actifs malgré les déconnexions et les redémarrages du navigateur, exécutez la commande dans tmux (tmux doit être installé sur l'hôte).", - "enableSessionLogging": "Journalisation de session", - "enableSessionLoggingDesc": "Enregistrer la sortie de la session terminal pour une consultation ultérieure", "localEchoLabel": "Écho local", "localEchoDesc": "Prédiction locale des frappes au clavier imprimables en toute sécurité, même sur les connexions lentes. Les invites de mot de passe et les entrées de contrôle ne sont jamais affichées.", "localEchoDefault": "Utiliser les paramètres globaux", @@ -1969,23 +1966,6 @@ "disk": "Disque", "viewServerDetails": "Afficher les détails du serveur" }, - "sessionLogs": { - "title": "Journaux de session", - "noLogs": "Aucun journal de session pour l’instant", - "noLogsDesc": "Activez la journalisation des sessions sur un hôte pour démarrer l’enregistrement", - "duration": "Durée", - "viewLog": "Afficher le journal", - "downloadLog": "Télécharger", - "downloadAsText": "Télécharger au format texte", - "deleteLog": "Supprimer", - "confirmDelete": "Supprimer ce journal de session ?", - "confirmDeleteDesc": "Cette action est irréversible.", - "copyContent": "Copier", - "copied": "Copié !", - "loadError": "Échec du chargement des journaux de session", - "deleteError": "Échec de la suppression du journal de session", - "filterByHost": "Filtrer par hôte..." - }, "admin": { "sectionGeneral": "Général", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/he_IL.json b/src/ui/locales/translated/he_IL.json index 5c16b1f21..36e4e78a4 100644 --- a/src/ui/locales/translated/he_IL.json +++ b/src/ui/locales/translated/he_IL.json @@ -695,7 +695,6 @@ "quickConnect": "חיבור מהיר", "sshTools": "כלי SSH", "history": "הִיסטוֹרִיָה", - "sessionLogs": "יומני סשן", "openAsTabHint": "לחיצה על Ctrl או לחיצה אמצעית כדי לפתוח ככרטיסייה", "openAsTab": "פתח ככרטיסייה", "openInRightDock": "פתח בעגינה הימנית", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "העדיפו Mosh על פני SSH אם זמין", "enableAutoTmux": "הפעל את Auto-Tmux", "enableAutoTmuxDesc": "שמרו על המעטפת שלכם וכל דבר שפועל בה בחיים גם לאחר ניתוקים והפעלות מחדש של הדפדפן על ידי הפעלתה בתוך tmux (tmux חייב להיות מותקן על המארח)", - "enableSessionLogging": "רישום מושבים", - "enableSessionLoggingDesc": "הקלט את פלט הפעלת הטרמינל לצורך סקירה מאוחרת יותר", "localEchoLabel": "הד מקומי", "localEchoDesc": "חזה הקשות מקלדת להדפסה בטוחות באופן מקומי בחיבורים איטיים. בקשות סיסמה וקלט בקרה לעולם לא יוחזרו.", "localEchoDefault": "השתמש בהגדרה גלובלית", @@ -1969,23 +1966,6 @@ "disk": "דִיסק", "viewServerDetails": "הצג פרטי שרת" }, - "sessionLogs": { - "title": "יומני סשן", - "noLogs": "אין עדיין יומני סשן", - "noLogsDesc": "הפעל רישום סשן במארח כדי להתחיל להקליט", - "duration": "מֶשֶׁך", - "viewLog": "הצג יומן", - "downloadLog": "הורדה", - "downloadAsText": "הורד כטקסט", - "deleteLog": "לִמְחוֹק", - "confirmDelete": "למחוק את יומן הפעילויות הזה?", - "confirmDeleteDesc": "לא ניתן לבטל פעולה זו.", - "copyContent": "לְהַעְתִיק", - "copied": "מוּעֲתָק!", - "loadError": "טעינת יומני הסשן נכשלה", - "deleteError": "נכשלה מחיקת יומן ההפעלה", - "filterByHost": "סנן לפי מארח..." - }, "admin": { "sectionGeneral": "כְּלָלִי", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/hi_IN.json b/src/ui/locales/translated/hi_IN.json index aa0446468..8ea9c48ed 100644 --- a/src/ui/locales/translated/hi_IN.json +++ b/src/ui/locales/translated/hi_IN.json @@ -695,7 +695,6 @@ "quickConnect": "त्वरित कनेक्ट", "sshTools": "SSH टूल्स", "history": "इतिहास", - "sessionLogs": "सत्र लॉग्स", "openAsTabHint": "Ctrl+क्लिक या मिडिल क्लिक करके इसे टैब के रूप में खोलें", "openAsTab": "टैब के रूप में खोलें", "openInRightDock": "दाईं ओर के डॉक में खोलें", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "यदि उपलब्ध हो तो SSH के बजाय Mosh को प्राथमिकता दें", "enableAutoTmux": "ऑटो-tmux सक्षम करें", "enableAutoTmuxDesc": "अपने शेल और उसमें चल रहे किसी भी चीज़ को डिस्कनेक्ट होने और ब्राउज़र के रीस्टार्ट होने पर भी चालू रखने के लिए, tmux के अंदर चलाएँ (होस्ट पर tmux इंस्टॉल होना चाहिए)।", - "enableSessionLogging": "सत्र लॉगिंग", - "enableSessionLoggingDesc": "बाद में समीक्षा के लिए टर्मिनल सत्र का आउटपुट रिकॉर्ड करें", "localEchoLabel": "स्थानीय प्रतिध्वनि", "localEchoDesc": "धीमे कनेक्शन पर स्थानीय रूप से सुरक्षित और प्रिंट करने योग्य कीस्ट्रोक्स का अनुमान लगाएं। पासवर्ड प्रॉम्प्ट और नियंत्रण इनपुट कभी भी प्रदर्शित नहीं होते हैं।", "localEchoDefault": "वैश्विक सेटिंग का उपयोग करें", @@ -1969,23 +1966,6 @@ "disk": "डिस्क", "viewServerDetails": "सर्वर विवरण देखें" }, - "sessionLogs": { - "title": "सेशन लॉग्स", - "noLogs": "अभी तक कोई सेशन लॉग नहीं", - "noLogsDesc": "रिकॉर्डिंग शुरू करने के लिए किसी होस्ट पर सेशन लॉगिंग सक्षम करें", - "duration": "अवधि", - "viewLog": "लॉग देखें", - "downloadLog": "डाउनलोड करें", - "downloadAsText": "टेक्स्ट के रूप में डाउनलोड करें", - "deleteLog": "हटाएं", - "confirmDelete": "क्या इस सेशन लॉग को हटाएं?", - "confirmDeleteDesc": "यह क्रिया वापस नहीं ली जा सकती।", - "copyContent": "कॉपी करें", - "copied": "कॉपी हो गया!", - "loadError": "सेशन लॉग लोड करने में विफल", - "deleteError": "सेशन लॉग हटाने में विफल", - "filterByHost": "होस्ट द्वारा फ़िल्टर करें..." - }, "admin": { "sectionGeneral": "सामान्य", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/hu_HU.json b/src/ui/locales/translated/hu_HU.json index 62b7b5066..a963db489 100644 --- a/src/ui/locales/translated/hu_HU.json +++ b/src/ui/locales/translated/hu_HU.json @@ -695,7 +695,6 @@ "quickConnect": "Gyors csatlakozás", "sshTools": "SSH eszközök", "history": "Történelem", - "sessionLogs": "Munkamenet-naplók", "openAsTabHint": "Ctrl+kattintás vagy középső kattintás fülként való megnyitáshoz", "openAsTab": "Megnyitás lapként", "openInRightDock": "Megnyitás a jobb oldali dokkban", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Ha elérhető, akkor a Mosh-t részesítsd előnyben az SSH-val szemben.", "enableAutoTmux": "Auto-Tmux engedélyezése", "enableAutoTmuxDesc": "Tartsd életben a shell-edet és a benne futó dolgokat a lecsatlakozások és a böngésző újraindítása során a tmux-on belüli futtatással (a tmux-nak telepítve kell lennie a gazdagépen)", - "enableSessionLogging": "Munkamenet-naplózás", - "enableSessionLoggingDesc": "Terminál munkamenet kimenetének rögzítése későbbi áttekintés céljából", "localEchoLabel": "Helyi visszhang", "localEchoDesc": "Lassú kapcsolatok esetén is helyben előrejelzi a biztonságosan nyomtatható billentyűleütéseket. A jelszókérések és a vezérlőbevitel soha nem jelenik meg.", "localEchoDefault": "Globális beállítás használata", @@ -1969,23 +1966,6 @@ "disk": "Korong", "viewServerDetails": "Szerver részleteinek megtekintése" }, - "sessionLogs": { - "title": "Munkamenet-naplók", - "noLogs": "Még nincsenek munkamenet-naplók", - "noLogsDesc": "A felvétel megkezdéséhez engedélyezze a munkamenet-naplózást egy gazdagépen", - "duration": "Időtartam", - "viewLog": "Napló megtekintése", - "downloadLog": "Letöltés", - "downloadAsText": "Letöltés szövegként", - "deleteLog": "Töröl", - "confirmDelete": "Törli ezt a munkamenet-naplót?", - "confirmDeleteDesc": "Ez a művelet nem vonható vissza.", - "copyContent": "Másolat", - "copied": "Másolva!", - "loadError": "Nem sikerült betölteni a munkamenet-naplókat", - "deleteError": "Nem sikerült törölni a munkamenet-naplót", - "filterByHost": "Szűrés hoszt szerint..." - }, "admin": { "sectionGeneral": "Általános", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/id_ID.json b/src/ui/locales/translated/id_ID.json index 6b8f847a4..446ba389a 100644 --- a/src/ui/locales/translated/id_ID.json +++ b/src/ui/locales/translated/id_ID.json @@ -695,7 +695,6 @@ "quickConnect": "Koneksi Cepat", "sshTools": "Alat SSH", "history": "Sejarah", - "sessionLogs": "Log Sesi", "openAsTabHint": "Tekan Ctrl+klik atau klik tengah untuk membuka sebagai tab.", "openAsTab": "Buka sebagai tab", "openInRightDock": "Buka di dermaga kanan", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Lebih baik menggunakan Mosh daripada SSH jika tersedia.", "enableAutoTmux": "Aktifkan Auto-Tmux", "enableAutoTmuxDesc": "Jaga agar shell Anda dan semua yang berjalan di dalamnya tetap aktif meskipun terjadi pemutusan koneksi dan restart browser dengan menjalankan perintah di dalam tmux (tmux harus diinstal pada host).", - "enableSessionLogging": "Pencatatan Sesi", - "enableSessionLoggingDesc": "Rekam output sesi terminal untuk ditinjau nanti.", "localEchoLabel": "Gema Lokal", "localEchoDesc": "Memprediksi penekanan tombol yang aman dan dapat dicetak secara lokal pada koneksi lambat. Permintaan kata sandi dan input kontrol tidak pernah diulang.", "localEchoDefault": "Gunakan pengaturan global", @@ -1969,23 +1966,6 @@ "disk": "Disk", "viewServerDetails": "Lihat detail server" }, - "sessionLogs": { - "title": "Log Sesi", - "noLogs": "Belum ada log sesi.", - "noLogsDesc": "Aktifkan pencatatan sesi pada host untuk memulai perekaman.", - "duration": "Lamanya", - "viewLog": "Lihat log", - "downloadLog": "Unduh", - "downloadAsText": "Unduh sebagai teks", - "deleteLog": "Menghapus", - "confirmDelete": "Hapus log sesi ini?", - "confirmDeleteDesc": "Tindakan ini tidak dapat dibatalkan.", - "copyContent": "Menyalin", - "copied": "Disalin!", - "loadError": "Gagal memuat log sesi", - "deleteError": "Gagal menghapus log sesi", - "filterByHost": "Saring berdasarkan host..." - }, "admin": { "sectionGeneral": "Umum", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/it_IT.json b/src/ui/locales/translated/it_IT.json index 8134cbc87..97cafbf14 100644 --- a/src/ui/locales/translated/it_IT.json +++ b/src/ui/locales/translated/it_IT.json @@ -695,7 +695,6 @@ "quickConnect": "Connessione rapida", "sshTools": "Strumenti SSH", "history": "Cronologia", - "sessionLogs": "Log di sessione", "openAsTabHint": "Ctrl+clic o clic centrale per aprire in una scheda", "openAsTab": "Apri come scheda", "openInRightDock": "Apri nella sezione dock a destra", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Preferisci Mosh rispetto a SSH se disponibile", "enableAutoTmux": "Abilita Auto-Tmux", "enableAutoTmuxDesc": "Mantieni la tua shell e tutto ciò che è in esecuzione al suo interno attivi anche in caso di disconnessioni e riavvii del browser, eseguendoli all'interno di tmux (tmux deve essere installato sull'host).", - "enableSessionLogging": "Registrazione sessione", - "enableSessionLoggingDesc": "Registra l'output della sessione del terminale per la revisione successiva", "localEchoLabel": "Eco locale", "localEchoDesc": "Prevedi localmente le sequenze di tasti sicure e stampabili anche con connessioni lente. Le richieste di password e gli input di controllo non vengono mai ripetuti.", "localEchoDefault": "Utilizza le impostazioni globali", @@ -1969,23 +1966,6 @@ "disk": "Disco", "viewServerDetails": "Visualizza dettagli server" }, - "sessionLogs": { - "title": "Registri di sessione", - "noLogs": "Ancora nessun registro di sessione", - "noLogsDesc": "Abilita la registrazione delle sessioni su un host per iniziare a registrare", - "duration": "Durata", - "viewLog": "Visualizza registro", - "downloadLog": "Scarica", - "downloadAsText": "Scarica come testo", - "deleteLog": "Elimina", - "confirmDelete": "Eliminare questo registro di sessione?", - "confirmDeleteDesc": "Questa azione non può essere annullata.", - "copyContent": "Copia", - "copied": "Copiato!", - "loadError": "Impossibile caricare i registri di sessione", - "deleteError": "Impossibile eliminare il registro di sessione", - "filterByHost": "Filtra per host..." - }, "admin": { "sectionGeneral": "Generale", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/ja_JP.json b/src/ui/locales/translated/ja_JP.json index e8ed52662..fa5eb4a36 100644 --- a/src/ui/locales/translated/ja_JP.json +++ b/src/ui/locales/translated/ja_JP.json @@ -695,7 +695,6 @@ "quickConnect": "クイック接続", "sshTools": "SSHツール", "history": "履歴", - "sessionLogs": "セッションログ", "openAsTabHint": "タブとして開くには、Ctrlキーを押しながらクリックするか、マウスの中央ボタンをクリックしてください。", "openAsTab": "タブとして開く", "openInRightDock": "右側のドックで開く", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "利用可能な場合、SSH よりも Mosh を優先します", "enableAutoTmux": "Auto-Tmux を有効にする", "enableAutoTmuxDesc": "tmux内で実行することで、接続切断やブラウザの再起動後もシェルとその中で実行されているすべての処理を正常に実行状態に保つことができます(ホストにtmuxがインストールされている必要があります)。", - "enableSessionLogging": "セッションログ", - "enableSessionLoggingDesc": "後で確認できるようにターミナルセッションの出力を記録します", "localEchoLabel": "ローカルエコー", "localEchoDesc": "低速回線でも、印刷可能な安全なキー入力をローカルで予測します。パスワード入力や制御入力はエコーバックされません。", "localEchoDefault": "グローバル設定を使用する", @@ -1969,23 +1966,6 @@ "disk": "ディスク", "viewServerDetails": "サーバーの詳細を表示" }, - "sessionLogs": { - "title": "セッションログ", - "noLogs": "セッションログはまだありません", - "noLogsDesc": "ホストでセッションログを有効にして記録を開始してください", - "duration": "所要時間", - "viewLog": "ログを表示", - "downloadLog": "ダウンロード", - "downloadAsText": "テキストとしてダウンロード", - "deleteLog": "削除", - "confirmDelete": "このセッションログを削除しますか?", - "confirmDeleteDesc": "この操作は元に戻せません。", - "copyContent": "コピー", - "copied": "コピーしました!", - "loadError": "セッションログの読み込みに失敗しました", - "deleteError": "セッションログの削除に失敗しました", - "filterByHost": "ホストでフィルター..." - }, "admin": { "sectionGeneral": "全般", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/ko_KR.json b/src/ui/locales/translated/ko_KR.json index 2a828138e..3b168e68a 100644 --- a/src/ui/locales/translated/ko_KR.json +++ b/src/ui/locales/translated/ko_KR.json @@ -695,7 +695,6 @@ "quickConnect": "빠른 연결", "sshTools": "SSH 도구", "history": "기록", - "sessionLogs": "세션 로그", "openAsTabHint": "Ctrl 키를 누른 상태에서 클릭하거나 마우스 가운데 버튼을 클릭하여 탭으로 엽니다.", "openAsTab": "탭에서 열기", "openInRightDock": "오른쪽 도크에서 열기", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "사용 가능한 경우 SSH보다 Mosh를 우선합니다", "enableAutoTmux": "Auto-Tmux 활성화", "enableAutoTmuxDesc": "tmux 내부에서 실행하면 연결이 끊기거나 브라우저가 다시 시작되더라도 셸과 그 안에서 실행 중인 모든 작업이 계속 유지됩니다(호스트에 tmux가 설치되어 있어야 함).", - "enableSessionLogging": "세션 로깅", - "enableSessionLoggingDesc": "나중에 검토할 수 있도록 터미널 세션 출력을 기록합니다", "localEchoLabel": "로컬 에코", "localEchoDesc": "연결 속도가 느린 환경에서도 안전하게 입력 가능한 키 입력을 로컬에서 예측합니다. 비밀번호 입력 및 제어 입력은 절대 화면에 표시되지 않습니다.", "localEchoDefault": "전역 설정을 사용하세요", @@ -1969,23 +1966,6 @@ "disk": "디스크", "viewServerDetails": "서버 세부정보 보기" }, - "sessionLogs": { - "title": "세션 로그", - "noLogs": "아직 세션 로그 없음", - "noLogsDesc": "호스트에서 세션 로깅을 활성화하여 기록 시작", - "duration": "지속 시간", - "viewLog": "로그 보기", - "downloadLog": "다운로드", - "downloadAsText": "텍스트 파일로 다운로드", - "deleteLog": "삭제", - "confirmDelete": "이 세션 로그를 삭제하시겠습니까?", - "confirmDeleteDesc": "이 작업은 취소할 수 없습니다.", - "copyContent": "복사", - "copied": "복사됨!", - "loadError": "세션 로그 로드 실패", - "deleteError": "세션 로그 삭제 실패", - "filterByHost": "호스트로 필터링..." - }, "admin": { "sectionGeneral": "일반", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/nl_NL.json b/src/ui/locales/translated/nl_NL.json index 10d946c9a..73c34ebd8 100644 --- a/src/ui/locales/translated/nl_NL.json +++ b/src/ui/locales/translated/nl_NL.json @@ -695,7 +695,6 @@ "quickConnect": "Snel verbinden", "sshTools": "SSH-tools", "history": "Geschiedenis", - "sessionLogs": "Sessielogboeken", "openAsTabHint": "Ctrl+klik of middelste muisknop om te openen als een tabblad.", "openAsTab": "Openen als tabblad", "openInRightDock": "Openen in het rechter dock", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Geef de voorkeur aan Mosh boven SSH indien beschikbaar.", "enableAutoTmux": "Auto-Tmux inschakelen", "enableAutoTmuxDesc": "Zorg ervoor dat je shell en alles wat daarin draait, blijft functioneren, ook na een verbindingsoverbreking of een herstart van de browser, door tmux te gebruiken (tmux moet wel op de host geïnstalleerd zijn).", - "enableSessionLogging": "Sessieregistratie", - "enableSessionLoggingDesc": "Sla de uitvoer van de terminalsessie op voor latere beoordeling.", "localEchoLabel": "Local Echo", "localEchoDesc": "Voorspel lokaal veilige toetsaanslagen, ook bij trage internetverbindingen. Wachtwoordprompts en besturingsinvoer worden nooit weergegeven.", "localEchoDefault": "Gebruik de globale instelling", @@ -1969,23 +1966,6 @@ "disk": "Schijf", "viewServerDetails": "Servergegevens bekijken" }, - "sessionLogs": { - "title": "Sessielogboeken", - "noLogs": "Nog geen sessielogboeken", - "noLogsDesc": "Schakel sessielogging in op een host om te beginnen met opnemen.", - "duration": "Duur", - "viewLog": "Logboek bekijken", - "downloadLog": "Download", - "downloadAsText": "Downloaden als tekst", - "deleteLog": "Verwijderen", - "confirmDelete": "Dit sessielogboek verwijderen?", - "confirmDeleteDesc": "Deze actie kan niet ongedaan gemaakt worden.", - "copyContent": "Kopiëren", - "copied": "Gekopieerd!", - "loadError": "Het laden van de sessielogboeken is mislukt.", - "deleteError": "Het verwijderen van het sessielogboek is mislukt.", - "filterByHost": "Filteren op host..." - }, "admin": { "sectionGeneral": "Algemeen", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/no_NO.json b/src/ui/locales/translated/no_NO.json index 5966a46c4..19e790252 100644 --- a/src/ui/locales/translated/no_NO.json +++ b/src/ui/locales/translated/no_NO.json @@ -695,7 +695,6 @@ "quickConnect": "Hurtigtilkobling", "sshTools": "SSH-verktøy", "history": "Historie", - "sessionLogs": "Øktlogger", "openAsTabHint": "Ctrl+klikk eller midtklikk for å åpne som fane", "openAsTab": "Åpne som fane", "openInRightDock": "Åpne i høyre dokk", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Foretrekker Mosh fremfor SSH hvis tilgjengelig", "enableAutoTmux": "Aktiver Auto-Tmux", "enableAutoTmuxDesc": "Hold skallet ditt og alt som kjører i det i live på tvers av frakoblinger og omstart av nettleseren ved å kjøre det i tmux (tmux må være installert på verten)", - "enableSessionLogging": "Øktlogging", - "enableSessionLoggingDesc": "Ta opp utdata fra terminaløkten for senere gjennomgang", "localEchoLabel": "Lokalt ekko", "localEchoDesc": "Forutsi trygge utskrivbare tastetrykk lokalt på trege tilkoblinger. Passordforespørsler og kontrollinndata gjentas aldri.", "localEchoDefault": "Bruk global innstilling", @@ -1969,23 +1966,6 @@ "disk": "Disk", "viewServerDetails": "Vis serverdetaljer" }, - "sessionLogs": { - "title": "Øktlogger", - "noLogs": "Ingen øktlogger ennå", - "noLogsDesc": "Aktiver øktlogging på en vert for å starte opptaket", - "duration": "Varighet", - "viewLog": "Vis logg", - "downloadLog": "Last ned", - "downloadAsText": "Last ned som tekst", - "deleteLog": "Slett", - "confirmDelete": "Slette denne øktloggen?", - "confirmDeleteDesc": "Denne handlingen kan ikke angres.", - "copyContent": "Kopiere", - "copied": "Kopiert!", - "loadError": "Kunne ikke laste inn øktlogger", - "deleteError": "Kunne ikke slette øktloggen", - "filterByHost": "Filtrer etter vert..." - }, "admin": { "sectionGeneral": "General", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/pl_PL.json b/src/ui/locales/translated/pl_PL.json index 38248996d..a33da04fb 100644 --- a/src/ui/locales/translated/pl_PL.json +++ b/src/ui/locales/translated/pl_PL.json @@ -695,7 +695,6 @@ "quickConnect": "Szybkie połączenie", "sshTools": "Narzędzia SSH", "history": "Historia", - "sessionLogs": "Logi sesji", "openAsTabHint": "Ctrl+kliknięcie lub środkowy przycisk myszy, aby otworzyć jako kartę", "openAsTab": "Otwórz jako kartę", "openInRightDock": "Otwórz w prawym doku", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Jeśli to możliwe, preferuj Mosh zamiast SSH", "enableAutoTmux": "Włącz Auto-Tmux", "enableAutoTmuxDesc": "Utrzymuj działanie powłoki i wszystkiego, co w niej działa, nawet po rozłączeniu i ponownym uruchomieniu przeglądarki, uruchamiając ją wewnątrz tmux (tmux musi być zainstalowany na hoście).", - "enableSessionLogging": "Rejestrowanie sesji", - "enableSessionLoggingDesc": "Zapisuj wyjście sesji terminala do późniejszego przejrzenia", "localEchoLabel": "Lokalne echo", "localEchoDesc": "Przewiduj lokalnie bezpieczne, drukowalne znaki na wolnych łączach. Monity o hasło i znaki sterujące nigdy nie są powtarzane.", "localEchoDefault": "Użyj ustawienia globalnego", @@ -1969,23 +1966,6 @@ "disk": "Dysk", "viewServerDetails": "Wyświetl szczegóły serwera" }, - "sessionLogs": { - "title": "Logi sesji", - "noLogs": "Brak logów sesji", - "noLogsDesc": "Włącz rejestrowanie sesji na hoście, aby rozpocząć nagrywanie", - "duration": "Czas trwania", - "viewLog": "Wyświetl log", - "downloadLog": "Pobierz", - "downloadAsText": "Pobierz jako tekst", - "deleteLog": "Usuń", - "confirmDelete": "Usunąć ten log sesji?", - "confirmDeleteDesc": "Tej czynności nie można cofnąć.", - "copyContent": "Kopiuj", - "copied": "Skopiowano!", - "loadError": "Nie udało się załadować logów sesji", - "deleteError": "Nie udało się usunąć logu sesji", - "filterByHost": "Filtruj według hosta..." - }, "admin": { "sectionGeneral": "Ogólne", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/pt_BR.json b/src/ui/locales/translated/pt_BR.json index 56d6355c7..37f12f548 100644 --- a/src/ui/locales/translated/pt_BR.json +++ b/src/ui/locales/translated/pt_BR.json @@ -695,7 +695,6 @@ "quickConnect": "Conexão rápida", "sshTools": "Ferramentas SSH", "history": "História", - "sessionLogs": "Registros de sessão", "openAsTabHint": "Ctrl+clique ou clique com o botão do meio para abrir como uma aba", "openAsTab": "Abrir como guia", "openInRightDock": "Abrir no painel direito", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Prefira Mosh a SSH, se disponível.", "enableAutoTmux": "Ativar Auto-Tmux", "enableAutoTmuxDesc": "Mantenha seu shell e tudo o que estiver sendo executado nele em funcionamento mesmo após desconexões e reinicializações do navegador, executando o comando dentro do tmux (o tmux precisa estar instalado no host).", - "enableSessionLogging": "Registro de sessão", - "enableSessionLoggingDesc": "Registre a saída da sessão do terminal para revisão posterior.", "localEchoLabel": "Eco Local", "localEchoDesc": "Preveja sequências de teclas seguras e imprimíveis localmente em conexões lentas. Os prompts de senha e as entradas de controle nunca são repetidos.", "localEchoDefault": "Usar configuração global", @@ -1969,23 +1966,6 @@ "disk": "Disco", "viewServerDetails": "Ver detalhes do servidor" }, - "sessionLogs": { - "title": "Registros de sessão", - "noLogs": "Ainda não há registros de sessão.", - "noLogsDesc": "Habilite o registro de sessão em um host para iniciar a gravação.", - "duration": "Duração", - "viewLog": "Ver registro", - "downloadLog": "Download", - "downloadAsText": "Baixar como texto", - "deleteLog": "Excluir", - "confirmDelete": "Excluir este registro de sessão?", - "confirmDeleteDesc": "Esta ação não pode ser desfeita.", - "copyContent": "Cópia", - "copied": "Copiado!", - "loadError": "Falha ao carregar os registros da sessão.", - "deleteError": "Falha ao excluir o registro da sessão", - "filterByHost": "Filtrar por host..." - }, "admin": { "sectionGeneral": "Em geral", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/pt_PT.json b/src/ui/locales/translated/pt_PT.json index 537bfd257..f9452b3f0 100644 --- a/src/ui/locales/translated/pt_PT.json +++ b/src/ui/locales/translated/pt_PT.json @@ -695,7 +695,6 @@ "quickConnect": "Ligação Rápida", "sshTools": "Ferramentas SSH", "history": "Histórico", - "sessionLogs": "Registos de Sessão", "openAsTabHint": "Ctrl+clique ou clique com o botão do meio para abrir como separador", "openAsTab": "Abrir como separador", "openInRightDock": "Abrir no painel direito", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Preferir Mosh a SSH, se disponível", "enableAutoTmux": "Ativar Auto-Tmux", "enableAutoTmuxDesc": "Mantenha o seu shell e tudo o que nele está a ser executado em funcionamento mesmo após desconexões e reinicializações do browser, executando o comando dentro do tmux (o tmux necessita de estar instalado no host).", - "enableSessionLogging": "Registo de sessão", - "enableSessionLoggingDesc": "Gravar a saída da sessão de terminal para revisão posterior", "localEchoLabel": "Eco Local", "localEchoDesc": "Preveja sequências de teclas seguras e imprimíveis localmente em ligações lentas. Os avisos de palavra-passe e as entradas de controlo nunca são repetidos.", "localEchoDefault": "Usar configuração global", @@ -1969,23 +1966,6 @@ "disk": "Disco", "viewServerDetails": "Ver detalhes do servidor" }, - "sessionLogs": { - "title": "Registos de Sessão", - "noLogs": "Ainda sem registos de sessão", - "noLogsDesc": "Ative o registo de sessão num host para começar a gravar", - "duration": "Duração", - "viewLog": "Ver registo", - "downloadLog": "Transferir", - "downloadAsText": "Descarregar como texto", - "deleteLog": "Eliminar", - "confirmDelete": "Eliminar este registo de sessão?", - "confirmDeleteDesc": "Esta ação não pode ser desfeita.", - "copyContent": "Copiar", - "copied": "Copiado!", - "loadError": "Falha ao carregar registos de sessão", - "deleteError": "Falha ao eliminar registo de sessão", - "filterByHost": "Filtrar por host..." - }, "admin": { "sectionGeneral": "Geral", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/ro_RO.json b/src/ui/locales/translated/ro_RO.json index 265f0a98d..8cbc8e64e 100644 --- a/src/ui/locales/translated/ro_RO.json +++ b/src/ui/locales/translated/ro_RO.json @@ -695,7 +695,6 @@ "quickConnect": "Conectare rapidă", "sshTools": "Instrumente SSH", "history": "Istorie", - "sessionLogs": "Jurnalele de sesiune", "openAsTabHint": "Ctrl+clic sau clic mijloc pentru a deschide ca filă", "openAsTab": "Deschide ca filă", "openInRightDock": "Deschideți în docul din dreapta", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Preferă Mosh în locul SSH dacă este disponibil", "enableAutoTmux": "Activează Auto-Tmux", "enableAutoTmuxDesc": "Mențineți shell-ul și tot ce rulează în el în funcțiune după deconectări și reporniri ale browserului, rulând în interiorul tmux (tmux trebuie să fie instalat pe gazdă).", - "enableSessionLogging": "Înregistrarea sesiunilor", - "enableSessionLoggingDesc": "Înregistrați ieșirea sesiunii terminale pentru o revizuire ulterioară", "localEchoLabel": "Ecou local", "localEchoDesc": "Preziceți local apăsările de taste imprimabile în siguranță pe conexiuni lente. Solicitările de parolă și intrările de control nu sunt niciodată reluate.", "localEchoDefault": "Folosește setarea globală", @@ -1969,23 +1966,6 @@ "disk": "Disc", "viewServerDetails": "Vizualizați detaliile serverului" }, - "sessionLogs": { - "title": "Jurnalele de sesiune", - "noLogs": "Încă nu există jurnale de sesiune", - "noLogsDesc": "Activați înregistrarea în jurnal pe o gazdă pentru a începe înregistrarea", - "duration": "Durată", - "viewLog": "Vizualizare jurnal", - "downloadLog": "Descărcare", - "downloadAsText": "Descărcați ca text", - "deleteLog": "Şterge", - "confirmDelete": "Ștergeți acest jurnal de sesiune?", - "confirmDeleteDesc": "Această acțiune nu poate fi anulată.", - "copyContent": "Copie", - "copied": "Copiat!", - "loadError": "Nu s-au putut încărca jurnalele de sesiune", - "deleteError": "Nu s-a putut șterge jurnalul sesiunii", - "filterByHost": "Filtrare după gazdă..." - }, "admin": { "sectionGeneral": "General", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/ru_RU.json b/src/ui/locales/translated/ru_RU.json index 893335f4a..46c37b10f 100644 --- a/src/ui/locales/translated/ru_RU.json +++ b/src/ui/locales/translated/ru_RU.json @@ -695,7 +695,6 @@ "quickConnect": "Быстрое подключение", "sshTools": "Инструменты SSH", "history": "История", - "sessionLogs": "Журналы сессий", "openAsTabHint": "Нажмите Ctrl+клик или среднюю кнопку мыши, чтобы открыть вкладку.", "openAsTab": "Открыть как вкладку", "openInRightDock": "Открыть в правом доке", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Предпочитать Mosh вместо SSH, если доступен", "enableAutoTmux": "Включить Auto-Tmux", "enableAutoTmuxDesc": "Поддерживайте работоспособность вашей оболочки и всего, что в ней работает, даже при разрыве соединения и перезапуске браузера, запустив tmux (tmux должен быть установлен на хосте).", - "enableSessionLogging": "Логирование сессии", - "enableSessionLoggingDesc": "Записывать вывод терминальной сессии для последующего просмотра", "localEchoLabel": "Локальное эхо", "localEchoDesc": "Предсказывает безопасные нажатия клавиш для печати локально даже при медленном соединении. Запросы паролей и управляющий ввод никогда не отображаются.", "localEchoDefault": "Использовать глобальные настройки", @@ -1969,23 +1966,6 @@ "disk": "Диск", "viewServerDetails": "Просмотреть сведения о сервере" }, - "sessionLogs": { - "title": "Журналы сеансов", - "noLogs": "Пока нет журналов сеансов", - "noLogsDesc": "Включите ведение журнала сеансов на хосте, чтобы начать запись", - "duration": "Продолжительность", - "viewLog": "Просмотреть журнал", - "downloadLog": "Скачать", - "downloadAsText": "Скачать как текст", - "deleteLog": "Удалить", - "confirmDelete": "Удалить этот журнал сеансов?", - "confirmDeleteDesc": "Это действие нельзя отменить.", - "copyContent": "Копировать", - "copied": "Скопировано!", - "loadError": "Не удалось загрузить журналы сеансов", - "deleteError": "Не удалось удалить журнал сеанса", - "filterByHost": "Фильтровать по хосту..." - }, "admin": { "sectionGeneral": "Общие", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/sr_SP.json b/src/ui/locales/translated/sr_SP.json index f765a6094..332dcd23d 100644 --- a/src/ui/locales/translated/sr_SP.json +++ b/src/ui/locales/translated/sr_SP.json @@ -695,7 +695,6 @@ "quickConnect": "Брзо повезивање", "sshTools": "SSH алати", "history": "Историја", - "sessionLogs": "Записи сесија", "openAsTabHint": "Ctrl+клик или средњи клик за отварање као картице", "openAsTab": "Отвори као картицу", "openInRightDock": "Отвори у десном доку", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Преферирај Mosh у односу на SSH ако је доступан", "enableAutoTmux": "Омогући аутоматски Tmux", "enableAutoTmuxDesc": "Одржавајте своју шкољку и све што се у њој покреће активним након прекида везе и поновног покретања прегледача покретањем унутар tmux-а (tmux мора бити инсталиран на хосту)", - "enableSessionLogging": "Записивање сесије", - "enableSessionLoggingDesc": "Снимите излаз терминалне сесије за каснији преглед", "localEchoLabel": "Локални одјек", "localEchoDesc": "Предвиди безбедне притиске тастера за штампање локално на спорим везама. Упити за лозинку и контролни уноси се никада не понављају.", "localEchoDefault": "Користите глобална подешавања", @@ -1969,23 +1966,6 @@ "disk": "Диск", "viewServerDetails": "Погледајте детаље сервера" }, - "sessionLogs": { - "title": "Записи сесија", - "noLogs": "Још нема евиденција сесија", - "noLogsDesc": "Омогућите евидентирање сесије на хосту да бисте започели снимање", - "duration": "Трајање", - "viewLog": "Прикажи дневник", - "downloadLog": "Преузми", - "downloadAsText": "Преузми као текст", - "deleteLog": "Обриши", - "confirmDelete": "Да ли желите да обришете овај дневник сесије?", - "confirmDeleteDesc": "Ова радња се не може поништити.", - "copyContent": "Копија", - "copied": "Копирано!", - "loadError": "Учитавање евиденције сесије није успело", - "deleteError": "Брисање дневника сесије није успело", - "filterByHost": "Филтрирај по хосту..." - }, "admin": { "sectionGeneral": "Опште", "sectionOidc": "ОИДЦ", diff --git a/src/ui/locales/translated/sv_SE.json b/src/ui/locales/translated/sv_SE.json index 70d116962..d3b53a3a4 100644 --- a/src/ui/locales/translated/sv_SE.json +++ b/src/ui/locales/translated/sv_SE.json @@ -695,7 +695,6 @@ "quickConnect": "Snabbanslutning", "sshTools": "SSH-verktyg", "history": "Historia", - "sessionLogs": "Sessionsloggar", "openAsTabHint": "Ctrl+klicka eller mellanklicka för att öppna som flik", "openAsTab": "Öppna som flik", "openInRightDock": "Öppna i höger docka", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Föredra Mosh framför SSH om tillgängligt", "enableAutoTmux": "Aktivera Auto-Tmux", "enableAutoTmuxDesc": "Håll ditt skal och allt som körs i det vid liv även efter frånkopplingar och omstart av webbläsare genom att köra det inuti tmux (tmux måste vara installerat på värden)", - "enableSessionLogging": "Sessionsloggning", - "enableSessionLoggingDesc": "Spela in utdata från terminalsessionen för senare granskning", "localEchoLabel": "Lokalt eko", "localEchoDesc": "Förutsäg säkra utskrivbara tangenttryckningar lokalt på långsamma anslutningar. Lösenordsförfrågningar och kontrollinmatning upprepas aldrig.", "localEchoDefault": "Använd global inställning", @@ -1969,23 +1966,6 @@ "disk": "Disk", "viewServerDetails": "Visa serverinformation" }, - "sessionLogs": { - "title": "Sessionsloggar", - "noLogs": "Inga sessionsloggar ännu", - "noLogsDesc": "Aktivera sessionsloggning på en värd för att starta inspelning", - "duration": "Varaktighet", - "viewLog": "Visa logg", - "downloadLog": "Ladda ner", - "downloadAsText": "Ladda ner som text", - "deleteLog": "Radera", - "confirmDelete": "Radera den här sessionsloggen?", - "confirmDeleteDesc": "Den här åtgärden kan inte ångras.", - "copyContent": "Kopiera", - "copied": "Kopierad!", - "loadError": "Misslyckades med att läsa in sessionsloggar", - "deleteError": "Misslyckades med att radera sessionsloggen", - "filterByHost": "Filtrera efter värd..." - }, "admin": { "sectionGeneral": "Allmän", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/th_TH.json b/src/ui/locales/translated/th_TH.json index d0a6b7dc0..1e7ba230c 100644 --- a/src/ui/locales/translated/th_TH.json +++ b/src/ui/locales/translated/th_TH.json @@ -695,7 +695,6 @@ "quickConnect": "การเชื่อมต่อด่วน", "sshTools": "เครื่องมือ SSH", "history": "ประวัติศาสตร์", - "sessionLogs": "บันทึกเซสชัน", "openAsTabHint": "Ctrl+คลิก หรือคลิกเมาส์กลาง เพื่อเปิดเป็นแท็บ", "openAsTab": "เปิดเป็นแท็บ", "openInRightDock": "เปิดในแถบด้านขวา", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "หากมีตัวเลือก ให้เลือกใช้ Mosh แทน SSH", "enableAutoTmux": "เปิดใช้งาน Auto-Tmux", "enableAutoTmuxDesc": "รักษาการทำงานของเชลล์และโปรแกรมใดๆ ที่ทำงานอยู่ภายในเชลล์ให้คงอยู่ต่อไปแม้การตัดการเชื่อมต่อหรือการรีสตาร์ทเบราว์เซอร์ โดยการเรียกใช้คำสั่งภายใน tmux (ต้องติดตั้ง tmux บนเครื่องโฮสต์)", - "enableSessionLogging": "การบันทึกเซสชัน", - "enableSessionLoggingDesc": "บันทึกผลลัพธ์จากเทอร์มินัลเพื่อตรวจสอบในภายหลัง", "localEchoLabel": "เสียงสะท้อนในท้องถิ่น", "localEchoDesc": "คาดการณ์การกดแป้นพิมพ์ที่ปลอดภัยสำหรับการพิมพ์ในเครื่องบนการเชื่อมต่อที่ช้า ข้อความแจ้งให้ป้อนรหัสผ่านและการป้อนข้อมูลควบคุมจะไม่ถูกแสดงซ้ำ", "localEchoDefault": "ใช้การตั้งค่าทั่วโลก", @@ -1969,23 +1966,6 @@ "disk": "ดิสก์", "viewServerDetails": "ดูรายละเอียดเซิร์ฟเวอร์" }, - "sessionLogs": { - "title": "บันทึกเซสชัน", - "noLogs": "ยังไม่มีบันทึกการใช้งานใดๆ", - "noLogsDesc": "เปิดใช้งานการบันทึกเซสชันบนโฮสต์เพื่อเริ่มการบันทึก", - "duration": "ระยะเวลา", - "viewLog": "ดูบันทึก", - "downloadLog": "ดาวน์โหลด", - "downloadAsText": "ดาวน์โหลดเป็นข้อความ", - "deleteLog": "ลบ", - "confirmDelete": "ลบบันทึกการใช้งานนี้หรือไม่?", - "confirmDeleteDesc": "การกระทำนี้ไม่สามารถย้อนกลับได้", - "copyContent": "สำเนา", - "copied": "คัดลอกแล้ว!", - "loadError": "ไม่สามารถโหลดบันทึกเซสชันได้", - "deleteError": "ไม่สามารถลบบันทึกเซสชันได้", - "filterByHost": "กรองตามโฮสต์..." - }, "admin": { "sectionGeneral": "ทั่วไป", "sectionOidc": "โอไอดีซี", diff --git a/src/ui/locales/translated/tr_TR.json b/src/ui/locales/translated/tr_TR.json index 6f0e33c71..eee2d405d 100644 --- a/src/ui/locales/translated/tr_TR.json +++ b/src/ui/locales/translated/tr_TR.json @@ -695,7 +695,6 @@ "quickConnect": "Hızlı Bağlantı", "sshTools": "SSH Araçları", "history": "Geçmiş", - "sessionLogs": "Oturum Kayıtları", "openAsTabHint": "Sekme olarak açmak için Ctrl+tıklayın veya orta fare tuşuna tıklayın.", "openAsTab": "Sekme olarak aç", "openInRightDock": "Sağ taraftaki yuvayı açın", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Varsa, SSH yerine Mosh'u tercih et", "enableAutoTmux": "Otomatik Tmux'u Etkinleştir", "enableAutoTmuxDesc": "Kabuk uygulamanızı ve içinde çalışan her şeyi bağlantı kesintileri ve tarayıcı yeniden başlatmaları sırasında çalışır durumda tutmak için tmux içinde çalıştırın (tmux ana bilgisayarda kurulu olmalıdır).", - "enableSessionLogging": "Oturum Kaydı", - "enableSessionLoggingDesc": "Daha sonra gözden geçirmek için terminal oturumu çıktısını kaydet", "localEchoLabel": "Yerel Yankı", "localEchoDesc": "Yavaş bağlantılarda bile yerel olarak güvenli yazdırılabilir tuş vuruşlarını tahmin edin. Parola istemleri ve kontrol girişleri asla tekrarlanmaz.", "localEchoDefault": "Genel ayarı kullanın", @@ -1969,23 +1966,6 @@ "disk": "Disk", "viewServerDetails": "Sunucu ayrıntılarını görüntüle" }, - "sessionLogs": { - "title": "Oturum Günlükleri", - "noLogs": "Henüz oturum günlüğü yok", - "noLogsDesc": "Kayda başlamak için bir sunucuda oturum günlüğünü etkinleştirin", - "duration": "Süre", - "viewLog": "Günlüğü görüntüle", - "downloadLog": "İndir", - "downloadAsText": "Metin olarak indir", - "deleteLog": "Sil", - "confirmDelete": "Bu oturum günlüğü silinsin mi?", - "confirmDeleteDesc": "Bu işlem geri alınamaz.", - "copyContent": "Kopyala", - "copied": "Kopyalandı!", - "loadError": "Oturum günlükleri yüklenemedi", - "deleteError": "Oturum günlüğü silinemedi", - "filterByHost": "Sunucuya göre filtrele..." - }, "admin": { "sectionGeneral": "Genel", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/uk_UA.json b/src/ui/locales/translated/uk_UA.json index 9907fab86..8c767e003 100644 --- a/src/ui/locales/translated/uk_UA.json +++ b/src/ui/locales/translated/uk_UA.json @@ -695,7 +695,6 @@ "quickConnect": "Швидке підключення", "sshTools": "SSH-інструменти", "history": "Історія", - "sessionLogs": "Журнали сеансів", "openAsTabHint": "Ctrl+клік або клік середньою кнопкою миші, щоб відкрити як вкладку", "openAsTab": "Відкрити як вкладку", "openInRightDock": "Відкрити в правому доку", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Надавати перевагу Mosh над SSH, якщо доступно", "enableAutoTmux": "Увімкнути автоматичне Tmux", "enableAutoTmuxDesc": "Зберігайте вашу оболонку та все, що в ній працює, активними після відключень та перезапусків браузера, запускаючи її всередині tmux (tmux має бути встановлений на хості)", - "enableSessionLogging": "Ведення журналу сеансу", - "enableSessionLoggingDesc": "Запис виводу сеансу терміналу для подальшого перегляду", "localEchoLabel": "Локальне відлуння", "localEchoDesc": "Передбачати безпечні для друку натискання клавіш локально на повільних з'єднаннях. Запити на введення пароля та введені елементи керування ніколи не відображаються.", "localEchoDefault": "Використовувати глобальні налаштування", @@ -1969,23 +1966,6 @@ "disk": "Диск", "viewServerDetails": "Переглянути деталі сервера" }, - "sessionLogs": { - "title": "Журнали сеансів", - "noLogs": "Журналів сеансів поки що немає", - "noLogsDesc": "Увімкнути ведення журналу сеансу на хості, щоб розпочати запис", - "duration": "Тривалість", - "viewLog": "Переглянути журнал", - "downloadLog": "Завантажити", - "downloadAsText": "Завантажити як текст", - "deleteLog": "Видалити", - "confirmDelete": "Видалити цей журнал сеансу?", - "confirmDeleteDesc": "Цю дію не можна скасувати.", - "copyContent": "Копіювати", - "copied": "Скопійовано!", - "loadError": "Не вдалося завантажити журнали сеансу", - "deleteError": "Не вдалося видалити журнал сеансу", - "filterByHost": "Фільтрувати за хостом..." - }, "admin": { "sectionGeneral": "Загальне", "sectionOidc": "ОІДК", diff --git a/src/ui/locales/translated/vi_VN.json b/src/ui/locales/translated/vi_VN.json index f45ad6507..34f97e8bc 100644 --- a/src/ui/locales/translated/vi_VN.json +++ b/src/ui/locales/translated/vi_VN.json @@ -695,7 +695,6 @@ "quickConnect": "Kết nối nhanh", "sshTools": "Công cụ SSH", "history": "Lịch sử", - "sessionLogs": "Nhật ký phiên", "openAsTabHint": "Nhấn Ctrl + chuột hoặc chuột giữa để mở dưới dạng tab", "openAsTab": "Mở dưới dạng tab", "openInRightDock": "Mở trong thanh công cụ bên phải", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "Ưu tiên Mosh hơn SSH nếu có", "enableAutoTmux": "Kích hoạt Auto-Tmux", "enableAutoTmuxDesc": "Giữ cho shell và mọi thứ đang chạy trong đó hoạt động bình thường ngay cả khi bị ngắt kết nối hoặc khởi động lại trình duyệt bằng cách chạy bên trong tmux (tmux phải được cài đặt trên máy chủ).", - "enableSessionLogging": "Ghi nhật ký phiên", - "enableSessionLoggingDesc": "Ghi lại đầu ra phiên terminal để xem lại sau", "localEchoLabel": "Tiếng vọng địa phương", "localEchoDesc": "Dự đoán các thao tác gõ phím an toàn có thể in được cục bộ trên các kết nối chậm. Lời nhắc nhập mật khẩu và đầu vào điều khiển không bao giờ được hiển thị lại.", "localEchoDefault": "Sử dụng cài đặt toàn cầu", @@ -1969,23 +1966,6 @@ "disk": "Ổ đĩa", "viewServerDetails": "Xem chi tiết máy chủ" }, - "sessionLogs": { - "title": "Nhật ký phiên", - "noLogs": "Chưa có nhật ký phiên nào", - "noLogsDesc": "Bật ghi nhật ký phiên trên máy chủ để bắt đầu ghi", - "duration": "Thời lượng", - "viewLog": "Xem nhật ký", - "downloadLog": "Tải xuống", - "downloadAsText": "Tải xuống dưới dạng văn bản", - "deleteLog": "Xóa", - "confirmDelete": "Xóa nhật ký phiên này?", - "confirmDeleteDesc": "Hành động này không thể hoàn tác.", - "copyContent": "Sao chép", - "copied": "Đã sao chép!", - "loadError": "Tải nhật ký phiên thất bại", - "deleteError": "Xóa nhật ký phiên thất bại", - "filterByHost": "Lọc theo máy chủ..." - }, "admin": { "sectionGeneral": "Chung", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/zh_CN.json b/src/ui/locales/translated/zh_CN.json index 40a2242fd..c2dfae73e 100644 --- a/src/ui/locales/translated/zh_CN.json +++ b/src/ui/locales/translated/zh_CN.json @@ -695,7 +695,6 @@ "quickConnect": "快速连接", "sshTools": "SSH 工具", "history": "历史记录", - "sessionLogs": "会话日志", "openAsTabHint": "按 Ctrl+单击或单击鼠标中键即可在新标签页中打开", "openAsTab": "在新标签页中打开", "openInRightDock": "在右侧停靠栏打开", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "若可用,优先使用 Mosh 而非 SSH", "enableAutoTmux": "启用自动 Tmux", "enableAutoTmuxDesc": "通过在 tmux 中运行(tmux 必须安装在主机上),可以在断开连接和浏览器重启后保持 shell 及其中运行的所有内容处于活动状态。", - "enableSessionLogging": "会话日志记录", - "enableSessionLoggingDesc": "记录终端会话输出以供日后查看", "localEchoLabel": "本地回声", "localEchoDesc": "在连接速度较慢的情况下,本地预测可安全打印的按键操作。密码提示和控制输入永远不会被回显。", "localEchoDefault": "使用全局设置", @@ -1969,23 +1966,6 @@ "disk": "磁盘", "viewServerDetails": "查看服务器详情" }, - "sessionLogs": { - "title": "会话日志", - "noLogs": "暂无会话日志", - "noLogsDesc": "在主机上启用会话日志以开始记录", - "duration": "持续时间", - "viewLog": "查看日志", - "downloadLog": "下载", - "downloadAsText": "下载为文本", - "deleteLog": "删除", - "confirmDelete": "删除此会话日志?", - "confirmDeleteDesc": "此操作无法撤消。", - "copyContent": "复制", - "copied": "已复制!", - "loadError": "加载会话日志失败", - "deleteError": "删除会话日志失败", - "filterByHost": "按主机筛选..." - }, "admin": { "sectionGeneral": "通用", "sectionOidc": "OIDC", diff --git a/src/ui/locales/translated/zh_TW.json b/src/ui/locales/translated/zh_TW.json index b139c8d7e..a9f07550a 100644 --- a/src/ui/locales/translated/zh_TW.json +++ b/src/ui/locales/translated/zh_TW.json @@ -695,7 +695,6 @@ "quickConnect": "快速連線", "sshTools": "SSH 工具", "history": "歷史記錄", - "sessionLogs": "工作階段記錄", "openAsTabHint": "按 Ctrl+按一下或按滑鼠中鍵即可在新標籤頁中開啟", "openAsTab": "在新分頁中開啟", "openInRightDock": "在右側碼頭打開", @@ -1033,8 +1032,6 @@ "enableAutoMoshDesc": "如果可用,優先使用 Mosh 而非 SSH", "enableAutoTmux": "啟用自動 Tmux", "enableAutoTmuxDesc": "透過在 tmux 中執行(tmux 必須安裝在主機上),可以在斷開連線和瀏覽器重新啟動後保持 shell 及其中執行的所有內容處於活動狀態。", - "enableSessionLogging": "工作階段記錄", - "enableSessionLoggingDesc": "記錄終端機工作階段輸出以供日後檢視", "localEchoLabel": "本地迴聲", "localEchoDesc": "在連線速度較慢的情況下,本地預測可安全列印的按鍵操作。密碼提示和控制輸入永遠不會被回顯。", "localEchoDefault": "使用全域設定", @@ -1969,23 +1966,6 @@ "disk": "磁碟", "viewServerDetails": "檢視伺服器詳細資訊" }, - "sessionLogs": { - "title": "工作階段記錄", - "noLogs": "尚無任何工作階段記錄", - "noLogsDesc": "在主機上啟用工作階段記錄以開始錄製", - "duration": "持續時間", - "viewLog": "檢視記錄", - "downloadLog": "下載", - "downloadAsText": "下載為文字", - "deleteLog": "刪除", - "confirmDelete": "刪除此工作階段記錄?", - "confirmDeleteDesc": "此動作無法復原。", - "copyContent": "複製", - "copied": "已複製!", - "loadError": "載入工作階段記錄失敗", - "deleteError": "刪除工作階段記錄失敗", - "filterByHost": "依主機篩選..." - }, "admin": { "sectionGeneral": "一般", "sectionOidc": "OIDC", diff --git a/src/ui/shell/shell-layout.ts b/src/ui/shell/shell-layout.ts index 6d5c0cf87..8e2f230c1 100644 --- a/src/ui/shell/shell-layout.ts +++ b/src/ui/shell/shell-layout.ts @@ -21,7 +21,6 @@ import { getTabType } from "./tab-registry"; const CORE_CAPTURABLE = new Set([ "homepage", "termix-id", - "session-logs", "macros", "ssh-tools", ]); @@ -30,7 +29,6 @@ const CORE_CAPTURABLE = new Set([ const CORE_SINGLETON = new Set([ "homepage", "termix-id", - "session-logs", "macros", "ssh-tools", ]); diff --git a/src/ui/shell/tabUtils.tsx b/src/ui/shell/tabUtils.tsx index f0059cbc7..c21bd84cd 100644 --- a/src/ui/shell/tabUtils.tsx +++ b/src/ui/shell/tabUtils.tsx @@ -9,7 +9,6 @@ import { User, Fingerprint, Hammer, - ScrollText, } from "lucide-react"; import { lazy, Suspense } from "react"; import { useTranslation } from "react-i18next"; @@ -41,11 +40,6 @@ const HomepageCanvas = lazy(() => const TermixIdPanel = lazy(() => import("@/sidebar/TermixIdPanel").then((m) => ({ default: m.TermixIdPanel })), ); -const SessionLogsPanel = lazy(() => - import("@/sidebar/SessionLogsPanel").then((m) => ({ - default: m.SessionLogsPanel, - })), -); const MacrosPanel = lazy(() => import("@/sidebar/MacrosPanel").then((m) => ({ default: m.MacrosPanel })), ); @@ -124,8 +118,6 @@ export function tabIcon(type: TabType) { return ; case "termix-id": return ; - case "session-logs": - return ; case "macros": return ; @@ -229,13 +221,6 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) { , ); - case "session-logs": - return withTabSuspense( - - - , - ); - case "split-screen": return null; diff --git a/src/ui/sidebar/AppRail.tsx b/src/ui/sidebar/AppRail.tsx index d84f852b5..472b6696a 100644 --- a/src/ui/sidebar/AppRail.tsx +++ b/src/ui/sidebar/AppRail.tsx @@ -23,7 +23,6 @@ export type CoreRailView = | "quick-connect" | ToolsTab | "connections" - | "session-logs" | "user-profile" | "admin-settings"; diff --git a/src/ui/sidebar/HostEditor.tsx b/src/ui/sidebar/HostEditor.tsx index aa0344d8c..fd5a5ca75 100644 --- a/src/ui/sidebar/HostEditor.tsx +++ b/src/ui/sidebar/HostEditor.tsx @@ -1789,27 +1789,6 @@ export function HostEditor({ onChange={(v) => setField("autoMosh", v)} /> - - {t("hosts.enableSessionLoggingDesc")}{" "} - - {t("hosts.docsLink")} - - - } - > - setField("enableSessionLogging", v)} - /> -